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


403

ОПТОСОЗ 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()

Ответы:


283

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

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

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


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

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

17
Джон, это был абсолютно теоретический вопрос, поэтому у меня не было кода для профилирования. В прошлом я всегда следовал PEP, но сегодня я написал некоторый код, который заставил меня задуматься, правильно ли это было сделать. Спасибо за вашу помощь.
Адам Дж. Форстер

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

24
@halfhourhacks Python не будет повторно импортировать модуль, но ему все равно придется выполнить несколько инструкций, просто чтобы посмотреть, существует ли модуль / находится ли он в sys.modules / etc.
Джон Милликин

24
-1. Помещение импорта в функцию не обязательно требует больше времени. Пожалуйста, смотрите мой ответ на другой вопрос.
Ааронастерлинг

4
Один из вариантов использования - избегать циклического импорта (обычно нецелесообразно, но иногда это подходит). Иногда класс A в модуле m1 вызывает метод класса B в модуле m2, который создает другой экземпляр класса A. Если метод в классе B, который создает экземпляр класса A, выполняет импорт только после выполнения функции, которая создает экземпляр, круговой импорт исключается.
Сэм Свенбьоргхристиансенсен

80

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


3
Да, но можно попасть в ад зависимости.
eigenein

8
Если два модуля должны импортировать друг друга, что-то не так с кодом.
Анна

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

4
Когда X нужен Y, а Y нужен X, они либо являются двумя частями одной и той же идеи (то есть должны быть определены вместе), либо отсутствует абстракция.
GLRoman

59

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

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

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

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

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


4
Многие IDE упрощают рефакторинг кода, оптимизируя и автоматически импортируя необходимые модули в ваш файл. В большинстве случаев PyCharm и Eclipse приняли правильные решения для меня. Могу поспорить, что есть способ получить такое же поведение в emacs или vim.
brent.payne

3
Импорт внутри оператора if в глобальном пространстве имен по-прежнему является глобальным импортом. Это выведет аргументы (с использованием Python 3): def main(): print(sys.argv); if True: import sys; main();вам нужно будет обернуть if __name__=='__main__'функцию, чтобы создать новое пространство имен.
Дарцинон

4
Это кажется мне отличной причиной для импорта внутри функций, а не в глобальном масштабе. Я весьма удивлен, что никто не упомянул, что делал это по той же причине. Есть ли какие-либо существенные недостатки, кроме производительности и многословия?
водорослевые

@algal недостаток в том, что многие питоны ненавидят это, потому что вы нарушаете кодекс бодрости духа. Вы должны убедить членов своей команды. Нарушение производительности минимально. Иногда это даже быстрее, см. Stackoverflow.com/a/4789963/362951
с

Я обнаружил, что для рефакторинга чрезвычайно полезно помещать импорт близко к месту, где я его использую. Больше не нужно прокручивать вверх и обратно столько таймов. Я использую IDE, такие как pycharm или wing ide, а также использую их рефакторинг, но я не всегда хочу на них полагаться. Перемещение функций в другой модуль становится намного проще с этим альтернативным стилем импорта, и, как следствие, я значительно реорганизую.
с

39

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

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

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

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

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

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


1
Неверно, что первый работает лучше: wiki.python.org/moin/PythonSpeed/…
Джейсон Бейкер,

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

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

В мире IronPython начальный импорт намного дороже, чем в CPython;). Пример «ленивого импорта» в вашей ссылке, вероятно, является наилучшим общим обобщенным решением.
Курт Хагенлохер

Надеюсь, вы не возражаете, но я отредактировал это в вашем посте. Это полезная информация, чтобы знать.
Джейсон Бейкер

9

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

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

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


8

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

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

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

Например:

do_something_with_x(x)

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

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

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

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


Мы говорим о десятках миллисекунд стоимости запуска на модуль (на моей машине). Это не всегда незначительно, например, если это влияет на отзывчивость веб-приложения на щелчок пользователя.
Евгений Сергеев

6

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

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

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

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

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


2
-1. Основные накладные расходы на импорт происходят только в первый раз. Стоимость поиска модуля sys.modulesможет быть легко компенсирована экономией, связанной только с поиском локального имени вместо глобального имени.
Ааронастерлинг

6

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

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


6

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

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

4

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

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


4

Просто чтобы завершить ответ Мо и оригинальный вопрос:

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

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

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


4

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

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


4

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

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

Если вы импортируете в функцию (и), вы получите удар только для загрузки, если и когда одна из этих функций вызывается впервые. Как уже отмечали многие, если этого не произойдет, вы сэкономите время загрузки. Но если функция (и) вызывается много раз, вы получаете повторное, хотя и гораздо меньшее попадание (для проверки того, что оно было загружено; не для фактической повторной загрузки). С другой стороны, как отметил @aaronasterling, вы также немного экономите, потому что импорт внутри функции позволяет функции использовать немного более быстрый поиск локальных переменных для идентификации имени позже ( http://stackoverflow.com/questions/477096/python- import-coding-style / 4789963 # 4789963 ).

Вот результаты простого теста, который импортирует несколько вещей из функции. Время, о котором сообщается (в Python 2.7.14 на 2,3-ГГц Intel Core i7), показано ниже (2-ой вызов, принимающий больше, чем более поздние вызовы, кажется последовательным, хотя я не знаю почему).

 0 foo:   14429.0924 µs
 1 foo:      63.8962 µs
 2 foo:      10.0136 µs
 3 foo:       7.1526 µs
 4 foo:       7.8678 µs
 0 bar:       9.0599 µs
 1 bar:       6.9141 µs
 2 bar:       7.1526 µs
 3 bar:       7.8678 µs
 4 bar:       7.1526 µs

Код:

from __future__ import print_function
from time import time

def foo():
    import collections
    import re
    import string
    import math
    import subprocess
    return

def bar():
    import collections
    import re
    import string
    import math
    import subprocess
    return

t0 = time()
for i in xrange(5):
    foo()
    t1 = time()
    print("    %2d foo: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
    t0 = t1
for i in xrange(5):
    bar()
    t1 = time()
    print("    %2d bar: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
    t0 = t1

Изменения во время выполнения, вероятно, связаны с масштабированием частоты процессора в ответ на нагрузку. Лучше начинать тестирование скорости со секунды занятой работы, чтобы увеличить тактовую частоту процессора.
Хан Кван Нинхуйс

3

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

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


3

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


1

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

test.py

X=10
Y=11
Z=12
def add(i):
  i = i + 10

runlocal.py

from test import add, X, Y, Z

    def callme():
      x=X
      y=Y
      z=Z
      ladd=add 
      for i  in range(100000000):
        ladd(i)
        x+y+z

    callme()

run.py

from test import add, X, Y, Z

def callme():
  for i in range(100000000):
    add(i)
    X+Y+Z

callme()

Время на Linux показывает небольшой выигрыш

/usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python run.py 
    0:17.80 real,   17.77 user, 0.01 sys
/tmp/test$ /usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python runlocal.py 
    0:14.23 real,   14.22 user, 0.01 sys

настоящие настенные часы. пользователь время в программе. sys - время системных вызовов.

https://docs.python.org/3.5/reference/executionmodel.html#resolution-of-names


1

читабельность

Помимо производительности при запуске, для локализующих importоператоров необходимо сделать аргумент читабельности . Например, возьмем строки Python с номерами с 1283 по 1296 в моем текущем первом проекте Python:

listdata.append(['tk font version', font_version])
listdata.append(['Gtk version', str(Gtk.get_major_version())+"."+
                 str(Gtk.get_minor_version())+"."+
                 str(Gtk.get_micro_version())])

import xml.etree.ElementTree as ET

xmltree = ET.parse('/usr/share/gnome/gnome-version.xml')
xmlroot = xmltree.getroot()
result = []
for child in xmlroot:
    result.append(child.text)
listdata.append(['Gnome version', result[0]+"."+result[1]+"."+
                 result[2]+" "+result[3]])

Если бы importвыписка была в начале файла, мне пришлось бы пролистать длинный путь или нажать Home, чтобы узнать, что ETбыло. Затем я должен вернуться к строке 1283, чтобы продолжить чтение кода.

Действительно, даже если import оператор находится в верхней части функции (или класса), как многие его разместят, потребуется подкачка вверх и вниз.

Отображение номера версии Gnome будет выполняться редко, поэтому importв начале файла вводится ненужная задержка запуска.


0

Я хотел бы упомянуть мой случай использования, очень похожий на упомянутый @John Millikin и @VK:

Дополнительный импорт

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

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


0

Это увлекательная дискуссия. Как и многие другие, я никогда не рассматривал эту тему. Я был вынужден импортировать функции из-за желания использовать Django ORM в одной из моих библиотек. Мне пришлось позвонить django.setup()перед импортом моих классов моделей, и, поскольку это было в начале файла, его перетаскивали в совершенно не код библиотеки Django из-за конструкции инжектора IoC.

Я немного взломался и в итоге django.setup()поместил конструктор singleton и соответствующий импорт в начало каждого метода класса. Теперь это работало нормально, но меня это беспокоило, потому что импорт не был на вершине, а также я начал беспокоиться о дополнительном времени импорта. Потом я пришел сюда и с большим интересом прочитал, как все это воспринимают.

У меня длинный опыт работы с C ++, и теперь я использую Python / Cython. Я считаю, что почему бы не включить импорт в функцию, если это не вызывает профилированного узкого места. Это все равно что объявить пространство для переменных непосредственно перед тем, как они вам понадобятся. Беда в том, что у меня есть тысячи строк кода со всем импортом вверху! Так что я думаю, что я буду делать это с сегодняшнего дня и менять нечетные файлы здесь и там, когда я прохожу и у меня есть время.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.