Должны ли операторы импорта всегда быть наверху модуля? - 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 ]

4 голосов
/ 04 апреля 2016

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

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

4 голосов
/ 01 июля 2013

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

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

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

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

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

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

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

3 голосов
/ 29 ноября 2016

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

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

2 голосов
/ 11 мая 2018

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

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

Если вы импортируете в функцию (и), то вы получаете удар только для загрузки , если и , когда одна из этих функций вызывается первой. Как уже отмечали многие, если этого не произойдет, вы сэкономите время загрузки. Но если функция (и) вызывается много раз, вы получаете повторное, хотя и гораздо меньшее попадание (для проверки того, что оно загружено ; не для фактической повторной загрузки). С другой стороны, как указал @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
2 голосов
/ 10 апреля 2018

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

1 голос
/ 06 ноября 2018

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

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

0 голосов
/ 05 июля 2018

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

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

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

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

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