Должны ли операторы импорта Python всегда находиться в верхней части модуля?

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()

Ответ 1

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

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

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


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

  • Дополнительная поддержка библиотеки. Если ваш код имеет несколько путей, которые используют разные библиотеки, не разбивайте их, если не установлена ​​дополнительная библиотека.
  • В __init__.py плагина, который может быть импортирован, но фактически не используется. Примерами являются плагины Bazaar, которые используют bzrlib lazy-load framework.

Ответ 2

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

Ответ 3

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

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

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

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

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

Ответ 4

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

Во-первых, вы можете иметь модуль с unit test формы:

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()
[...]

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

Ответ 5

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

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

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

Ответ 6

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

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

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

Ответ 7

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

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

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

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

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

Ответ 8

Вот пример, где все импортные данные находятся на самом верху (это единственный раз, когда мне нужно было это сделать). Я хочу иметь возможность завершить подпроцесс как для 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

(В обзоре: что сказал Джон Милликин)

Ответ 9

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

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

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

Например:

do_something_with_x(x0

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

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

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

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

Ответ 10

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

from foo import bar
from baz import qux
# Note: datetime is imported in SomeClass below

Ответ 11

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

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

Ответ 12

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

Эта проблема часто возникает в Apache Spark Python API, где вам нужно инициализировать SparkContext перед импортом пакетов или модулей pyspark. Лучше всего разместить импорт pyspark в области, где гарантируется доступность SparkContext.

Ответ 13

Просто для завершения Moe answer и оригинального вопроса:

Когда мы имеем дело с круговыми зависимостями, мы можем сделать некоторые "трюки". Предполагая, что мы работаем с модулями a.py и b.py, которые содержат x() и b y(), соответственно. Тогда:

  • Мы можем переместить один из from imports в нижней части модуля.
  • Мы можем переместить один из from imports внутри функции или метода, который действительно требует импорта (это не всегда возможно, поскольку вы можете использовать его из нескольких мест).
  • Мы можем изменить один из двух from imports как импорт, который выглядит следующим образом: import a

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

Ответ 14

Я не стремлюсь дать полный ответ, потому что другие уже сделали это очень хорошо. Я просто хочу упомянуть один случай использования, когда особенно полезно импортировать модули внутри функций. Мое приложение использует пакеты и модули python, хранящиеся в определенном месте в виде плагинов. Во время запуска приложения приложение просматривает все модули в местоположении и импортирует их, затем он просматривает модули и обнаруживает некоторые точки монтирования для плагинов (в моем случае это подкласс определенного базового класса, имеющего уникальный ID) он регистрирует их. Количество плагинов велико (теперь десятки, но, возможно, сотни в будущем), и каждый из них используется довольно редко. Импорт сторонних библиотек в верхней части моих модулей плагинов был небольшим штрафом при запуске приложения. Особенно некоторые библиотеки третьих сторон очень тяжелы для импорта (например, импорт сюжетных сетей даже пытается подключиться к Интернету и загружать то, что добавлялось около одной секунды для запуска). Оптимизируя импорт (вызывая их только в тех функциях, где они используются) в плагинах мне удалось свернуть запуск с 10 секунд до примерно 2 секунд. Это большая разница для моих пользователей.

Итак, мой ответ - нет, не всегда помещайте импорт вверху ваших модулей.