Python - импортировать внутренние функции?

127

PEP 8 говорит:

  • Импорты всегда помещаются в начало файла, сразу после любых комментариев и строк документации, а также перед глобальными переменными и константами модуля.

Иногда я нарушаю PEP 8. Иногда я импортирую что-то внутри функций. Как правило, я делаю это, если есть импорт, который используется только в одной функции.

Есть мнения?

ИЗМЕНИТЬ (причина, по которой я считаю, что импорт функций может быть хорошей идеей):

Основная причина: это может сделать код более понятным.

  • Глядя на код функции, я могу спросить себя: «Что такое функция / класс xxx?» (xxx используется внутри функции). Если у меня есть весь мой импорт в верхней части модуля, я должен пойти посмотреть туда, чтобы определить, что такое xxx. Это больше проблема при использовании from m import xxx. Просмотр m.xxxфункции, вероятно, говорит мне больше. В зависимости от того, что mэто: хорошо известный модуль верхнего уровня / package ( import m)? Или это подмодуль / пакет ( from a.b.c import m)?
  • В некоторых случаях наличие этой дополнительной информации («Что такое xxx?») Рядом с тем, где используется xxx, может облегчить понимание функции.
codeape
источник
2
а вы это делаете для производительности?
Macarse,
4
Я чувствую, что в некоторых случаях это делает код более понятным. Я бы предположил, что необработанная производительность падает при импорте в функцию (поскольку оператор импорта будет выполняться каждый раз при вызове функции).
codeape
Вы можете ответить "Что такое функция / класс xxx?" с использованием синтаксиса import xyz, а не синтаксиса импорта from xyz abc
Том Лейс,
1
Если ясность является единственным фактором, U может также включить соответствующий комментарий на этот счет. ;)
Лакшман Прасад
5
@becomingGuru: Конечно, но комментарии могут не
совпадать

Ответы:

89

В конечном итоге, я думаю, вы оцените то, что большая часть вашего импорта находится в верхней части файла, таким образом вы можете сразу определить, насколько сложен ваш модуль по тому, что ему нужно импортировать.

Если я добавляю новый код в существующий файл, я обычно выполняю импорт там, где он нужен, а затем, если код остается, я делаю вещи более постоянными, перемещая строку импорта в верхнюю часть файла.

Еще один момент: я предпочитаю получать ImportErrorисключение перед запуском любого кода - в качестве проверки работоспособности, так что это еще одна причина для импорта вверху.

Использую pyCheckerдля проверки неиспользуемых модулей.

Питер Эриксон
источник
47

Я нарушаю PEP 8 в двух случаях:

  • Циклический импорт: модуль A импортирует модуль B, но что-то в модуле B нуждается в модуле A (хотя это часто является признаком того, что мне нужно реорганизовать модули, чтобы устранить циклическую зависимость)
  • Вставка точки останова pdb: import pdb; pdb.set_trace()это удобно, b / c я не хочу помещать import pdbвверху каждого модуля, который я мог бы захотеть отладить, и легко не забыть удалить импорт, когда я удаляю точку останова.

За пределами этих двух случаев рекомендуется размещать все наверху. Это делает зависимости более понятными.

Рик Коупленд
источник
7
Я согласен, что это делает зависимости более понятными в отношении модуля в целом. Но я считаю, что это может сделать код менее понятным на уровне функций, чтобы импортировать все наверху. Когда вы смотрите на код функции, вы можете спросить себя: «Что такое функция / класс xxx?» (xxx используется внутри функции). И вы должны посмотреть на самую верхнюю часть файла, чтобы увидеть, откуда берется xxx. Это больше проблема при использовании from m import xxx. Просмотр m.xxx говорит вам больше - по крайней мере, если нет сомнений в том, что такое m.
codeape
20

Вот четыре варианта использования импорта, которые мы используем

  1. importfrom x import yи import x as y) в верхней

  2. Выбор для импорта. На вершине.

    import settings
    if setting.something:
        import this as foo
    else:
        import that as foo
  3. Условный импорт. Используется с библиотеками JSON, XML и т. Д. На вершине.

    try:
        import this as foo
    except ImportError:
        import that as foo
  4. Динамический импорт. Пока что у нас есть только один пример этого.

    import settings
    module_stuff = {}
    module= __import__( settings.some_module, module_stuff )
    x = module_stuff['x']

    Обратите внимание, что этот динамический импорт не вводит код, но вводит сложные структуры данных, написанные на Python. Это что-то вроде маринованного фрагмента данных, за исключением того, что мы собирали его вручную.

    Это также более или менее находится в верхней части модуля.


Вот что мы делаем, чтобы код был понятнее:

  • Модули должны быть короткими.

  • Если у меня есть весь мой импорт в верхней части модуля, я должен пойти посмотреть туда, чтобы определить, что это за имя. Если модуль короткий, это легко сделать.

  • В некоторых случаях наличие этой дополнительной информации рядом с тем, где используется имя, может облегчить понимание функции. Если модуль короткий, это легко сделать.

С. Лотт
источник
Использование коротких модулей, конечно, очень хорошая идея. Но для того, чтобы всегда иметь «информацию об импорте» для доступных функций, максимальная длина модуля должна составлять один экран (возможно, максимум 100 строк). И это, вероятно, было бы слишком коротким, чтобы быть практичным в большинстве случаев.
codeape
Я полагаю, вы могли бы довести это до крайности. Я думаю, что может быть точка баланса, когда ваш модуль «достаточно мал», и вам не нужны изощренные методы импорта для управления сложностью. Наш средний размер модуля - по совпадению - около 100 строк.
S.Lott
8

Следует помнить об одном: ненужный импорт может вызвать проблемы с производительностью. Так что, если эта функция будет вызываться часто, вам лучше просто поместить импорт вверху. Конечно , это является оптимизацией, так что если есть действительный случай , который будет сделано , что импорт внутри функции является более ясным , чем импорт в верхней части файла, что козыри производительности в большинстве случаев.

Если вы используете IronPython, мне сказали, что лучше импортировать внутренние функции (поскольку компиляция кода в IronPython может быть медленной). Таким образом, вы можете получить способ импорта внутренних функций. Но кроме этого, я бы сказал, что бороться с условностями просто не стоит.

Как правило, я делаю это, если есть импорт, который используется только в одной функции.

Еще я хотел бы отметить, что это может быть потенциальной проблемой обслуживания. Что произойдет, если вы добавите функцию, которая использует модуль, который ранее использовался только одной функцией? Не забудьте добавить импорт в начало файла? Или вы собираетесь сканировать каждую функцию на предмет импорта?

FWIW, бывают случаи, когда имеет смысл импортировать внутри функции. Например, если вы хотите установить язык в cx_Oracle, вам необходимо установить _переменную среды NLS LANG перед ее импортом. Таким образом, вы можете увидеть такой код:

import os

oracle = None

def InitializeOracle(lang):
    global oracle
    os.environ['NLS_LANG'] = lang
    import cx_Oracle
    oracle = cx_Oracle
Джейсон Бейкер
источник
2
Я согласен с вашей проблемой обслуживания. Рефакторинг кода может быть немного проблематичным. Если я добавляю вторую функцию, которая использует модуль, который ранее использовался только одной функцией, я либо перемещаю импорт наверх, либо нарушаю собственное общее правило, импортируя модуль также во вторую функцию.
codeape
2
Я думаю, что аргумент о производительности может идти и по другому пути. Импорт модуля может занять много времени. В распределенных файловых системах, таких как суперкомпьютеры, импорт большого модуля, такого как numpy, может занять несколько секунд. Если модуль нужен только для одной, редко используемой функции, то импорт в функцию значительно ускорит общий случай.
amaurea
6

Я уже нарушал это правило для модулей, которые проходят самотестирование. То есть они обычно используются только для поддержки, но я определяю для них основную, так что если вы запустите их сами по себе, вы можете проверить их функциональность. В этом случае я иногда импортирую getoptи cmdпросто в основном, потому что я хочу, чтобы кто-то, читающий код, понял, что эти модули не имеют ничего общего с нормальной работой модуля и включаются только для тестирования.

Дэн Лью
источник
5

Исходя из вопроса о загрузке модуля дважды - а почему не оба?

Импорт в верхней части скрипта укажет зависимости и другой импорт в функции, сделав эту функцию более атомарной, при этом, по-видимому, не вызовет какого-либо снижения производительности, поскольку последовательный импорт дешев.

IljaBek
источник
3

Пока это importи неfrom x import * , вы должны поместить их вверху. Он добавляет только одно имя в глобальное пространство имен, и вы придерживаетесь PEP 8. Плюс, если оно вам позже понадобится в другом месте, вам не нужно ничего перемещать.

В этом нет ничего страшного, но поскольку разницы почти нет, я предлагаю делать то, что говорит PEP 8.

Хавьер
источник
3
Фактически, размещение from x import *функции внутри функции вызовет SyntaxWarning, по крайней мере, в 2.5.
Рик Коупленд,
3

Взгляните на альтернативный подход, который используется в sqlalchemy: внедрение зависимости:

@util.dependencies("sqlalchemy.orm.query")
def merge_result(query, *args):
    #...
    query.Query(...)

Обратите внимание, как импортированная библиотека объявляется в декораторе и передается в качестве аргумента функции !

Такой подход делает код чище, а также работает в 4,5 раза быстрее, чем importоператор!

Тест: https://gist.github.com/kolypto/589e84fbcfb6312532658df2fabdb796

kolypto
источник
2

В модулях, которые являются «обычными» модулями и могут выполняться (т.е. имеют if __name__ == '__main__': Е. -секцию), я обычно импортирую модули, которые используются только при выполнении модуля внутри основного раздела.

Пример:

def really_useful_function(data):
    ...


def main():
    from pathlib import Path
    from argparse import ArgumentParser
    from dataloader import load_data_from_directory

    parser = ArgumentParser()
    parser.add_argument('directory')
    args = parser.parse_args()
    data = load_data_from_directory(Path(args.directory))
    print(really_useful_function(data)


if __name__ == '__main__':
    main()
codeape
источник
1

Есть еще один (вероятно, «угловой») случай, когда это может быть полезно для importвнутренних редко используемых функций: сокращение времени запуска.

Однажды я столкнулся с этой проблемой, когда на небольшом IoT-сервере работала довольно сложная программа, которая принимает команды из последовательной линии и выполняет операции, возможно, очень сложные.

Размещение importоператоров в верхней части файлов означает, что все импорт будет обработан до запуска сервера; поскольку importсписок включены jinja2, lxml, signxmlи другие «тяжелые веса» (и SoC был не очень мощным) это означало минут до первой команды была фактически выполнены.

OTOH разместил большую часть импорта в функциях, и мне удалось подключить сервер к последовательной линии за секунды. Конечно, когда модули были действительно необходимы, мне пришлось заплатить цену (Примечание: также это можно смягчить, создав фоновую задачу, выполняющуюimport s во время простоя).

ZioByte
источник