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

StackOverflow https://stackoverflow.com/questions/128478

Вопрос

ПКП 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()
Это было полезно?

Решение

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

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

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


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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Например:

do_something_with_x(x)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Поэтому мой ответ — нет, не всегда размещайте импорт в верхней части модулей.

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

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

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

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

 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

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

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

test.py

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

запуститьlocal.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#solve-of-names

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top