Должны ли операторы импорта всегда быть наверху модуля? - PullRequest
346 голосов
/ 24 сентября 2008

PEP 08 состояния:

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

Однако, если импортируемый мной класс / метод / функция используется только в редких случаях, несомненно, более эффективно выполнять импорт, когда это необходимо?

Разве это не:

class SomeClass(object):

    def not_often_called(self)
        from datetime import datetime
        self.datetime = datetime.now()

эффективнее этого?

from datetime import datetime

class SomeClass(object):

    def not_often_called(self)
        self.datetime = datetime.now()

Ответы [ 18 ]

248 голосов
/ 24 сентября 2008

Модуль импортируется довольно быстро, но не мгновенно. Это означает, что:

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

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


Лучшие причины, по которым я видел ленивый импорт:

  • Дополнительная поддержка библиотеки. Если в вашем коде несколько путей, использующих разные библиотеки, не прерывайте работу, если дополнительная библиотека не установлена.
  • В __init__.py плагина, который может быть импортирован, но фактически не использован. Примерами являются плагины Bazaar, которые используют фреймворк с отложенной загрузкой bzrlib.
71 голосов
/ 25 сентября 2008

Размещение оператора импорта внутри функции может предотвратить циклические зависимости. Например, если у вас есть 2 модуля, X.py и Y.py, и им обоим необходимо импортировать друг друга, это приведет к циклической зависимости при импорте одного из модулей, вызывая бесконечный цикл. Если вы переместите оператор импорта в один из модулей, он не будет пытаться импортировать другой модуль до тех пор, пока не будет вызвана функция, и этот модуль уже будет импортирован, поэтому бесконечный цикл отсутствует. Подробнее читайте здесь - effbot.org / zone / import-confusion.htm

55 голосов
/ 24 сентября 2008

Я принял практику помещения всех импортов в функции, которые их используют, а не в верхнюю часть модуля.

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

Существует штраф за скорость, как упоминалось в другом месте. Я измерил это в своем заявлении и нашел, что это несущественно для моих целей.

Также приятно иметь возможность видеть все зависимости модуля без предварительного поиска (например, grep). Однако причина, по которой я беспокоюсь о зависимостях модулей, обычно заключается в том, что я устанавливаю, реорганизую или перемещаю всю систему, содержащую несколько файлов, а не только один модуль. В этом случае я все равно собираюсь выполнить глобальный поиск, чтобы убедиться, что у меня есть зависимости на уровне системы. Поэтому я не нашел глобального импорта, который помог бы мне понять систему на практике.

Обычно я помещаю импорт sys в проверку if __name__=='__main__', а затем передаю аргументы (например, sys.argv[1:]) в функцию main(). Это позволяет мне использовать main в контексте, где sys не был импортирован.

36 голосов
/ 24 сентября 2008

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

Во-первых, у вас может быть модуль с модульным тестом вида:

if __name__ == '__main__':
    import foo
    aa = foo.xyz()         # initiate something for the test

Во-вторых, у вас может возникнуть требование условного импорта какого-либо другого модуля во время выполнения.

if [condition]:
    import foo as plugin_api
else:
    import bar as plugin_api
xx = plugin_api.Plugin()
[...]

Возможно, есть другие ситуации, когда вы можете поместить импорт в другие части кода.

14 голосов
/ 24 сентября 2008

Первый вариант действительно более эффективен, чем второй, когда функция вызывается либо ноль, либо один раз. Однако при втором и последующих вызовах подход «импортировать каждый вызов» на самом деле менее эффективен. См. эту ссылку для техники отложенной загрузки, которая сочетает в себе лучшее из обоих подходов, выполняя "отложенный импорт".

Но есть и другие причины, помимо эффективности, почему вы можете предпочесть одну над другой. Один из подходов делает его более понятным для читателя кода относительно зависимостей этого модуля. Они также имеют очень разные характеристики сбоев - первый потерпит неудачу во время загрузки, если не будет модуля «datetime», а второй не потерпит неудачу, пока метод не будет вызван.

Добавлено примечание: В IronPython импорт может быть немного дороже, чем в CPython, поскольку код в основном компилируется при импорте.

8 голосов
/ 24 сентября 2008

Я бы не стал слишком сильно беспокоиться об эффективности загрузки модуля. Объем памяти, занимаемой модулем, не будет очень большим (при условии, что он достаточно модульный), а стоимость запуска будет незначительной.

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

Одной из веских причин для импорта модуля в другое место кода является его использование в операторе отладки.

Например:

do_something_with_x(x)

Я мог бы отладить это с помощью:

from pprint import pprint
pprint(x)
do_something_with_x(x)

Конечно, другая причина для импорта модулей в другое место кода - это необходимость динамического импорта их. Это потому, что у тебя практически нет выбора.

Я бы не стал слишком сильно беспокоиться об эффективности загрузки модуля. Объем памяти, занимаемой модулем, не будет очень большим (при условии, что он достаточно модульный), а стоимость запуска будет незначительной.

8 голосов
/ 24 сентября 2008

Курт делает хорошее замечание: вторая версия более понятна и потерпит неудачу во время загрузки, а не позже и неожиданно.

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

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

6 голосов
/ 24 сентября 2008

Это компромисс, который может решить только программист.

Случай 1 экономит некоторую память и время запуска, не импортируя модуль datetime (и не выполняя какую-либо инициализацию), пока он не понадобится. Обратите внимание, что выполнение импорта «только при вызове» также означает выполнение его «каждый раз при вызове», поэтому каждый вызов после первого по-прежнему сопряжен с дополнительными издержками при выполнении импорта.

Случай 2 экономит некоторое время выполнения и задержку, предварительно импортируя datetime, так что not_often_called () будет возвращаться быстрее при вызове , а также не тратя накладные расходы на импорт при каждом вызове.

Помимо эффективности, проще увидеть зависимости модуля заранее, если операторы импорта ... заранее. Скрытие их в коде может затруднить поиск модулей, от которых что-то зависит.

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

6 голосов
/ 24 сентября 2008

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

from foo import bar
from baz import qux
# Note: datetime is imported in SomeClass below
6 голосов
/ 24 сентября 2008

Вот пример, где весь импорт находится на самом верху (это единственный раз, когда мне нужно было это сделать). Я хочу иметь возможность завершить подпроцесс как в Un * x, так и в Windows.

import os
# ...
try:
    kill = os.kill  # will raise AttributeError on Windows
    from signal import SIGTERM
    def terminate(process):
        kill(process.pid, SIGTERM)
except (AttributeError, ImportError):
    try:
        from win32api import TerminateProcess  # use win32api if available
        def terminate(process):
            TerminateProcess(int(process._handle), -1)
    except ImportError:
        def terminate(process):
            raise NotImplementedError  # define a dummy function

(На рассмотрении: что Джон Милликин сказал.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...