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

PEP 8 заявляет:

Imports are always put at the top of the file, just after any module comments and docstrings, and before module globals and constants.

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

Разве это не так:

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()
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
440
0
124 873
20
Перейти к ответу Данный вопрос помечен как решенный

Ответы 20

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

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

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

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

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

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

Например:

do_something_with_x(x)

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

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

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

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

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

Evgeni Sergeev 26.03.2016 07:01

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

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

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

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

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

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

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

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

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

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

aaronasterling 25.01.2011 07:31

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

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

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

Ответ принят как подходящий

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

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

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


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

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

В этом ответе основное внимание уделяется стоимости инструкции import, но не импортируется стоимость какие. Примите во внимание импортируемую библиотеку. Некоторые библиотеки могут выполнять дорогостоящие операции (во время выполнения или затраты памяти) при импорте.

Dennis 07.11.2018 16:24

@Dennis, это потому, что начальная стоимость импорта постоянна, независимо от того, когда вы это делаете. Фактический процесс загрузки, о котором вы говорите, не происходит во второй раз. Ответ относится к проверке, которую необходимо выполнить в sys.modules при обнаружении оператора импорта.

Mad Physicist 09.01.2021 10:41

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

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

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

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

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

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

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

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

brent.payne 01.09.2011 01:13

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

Darcinon 05.01.2016 22:38

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

algal 23.01.2018 19:47

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

mit 10.11.2018 20:59

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

mit 10.11.2018 20:59

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

Ehren 12.11.2020 21:43

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

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

eigenein 17.11.2014 19:13

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

Anna 21.08.2018 14:41

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

Robert 02.12.2019 18:55

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

GLRoman 16.01.2020 21:48

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

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

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

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

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

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

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

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

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

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

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

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

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

Han-Kwang Nienhuys 07.06.2020 15:47

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

Необязательный импорт

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

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

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

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

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

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

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

Читаемость

Помимо производительности при запуске, есть аргумент в пользу удобочитаемости для локализации операторов import. Например, возьмите строки с номерами строк с 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 в верхней части файла приводит к ненужной задержке при запуске.

Что произойдет, если вы снова воспользуетесь ET в сценарии? Как читатель узнает, где искать исходный импорт, если он не находится в верхней части модуля? С помощью текстового поиска / поиска? Неудобство перехода в верхнюю часть модуля перевешивается возможностью быстро идентифицировать и поддерживать / сокращать весь импорт, используемый модулем, когда код изменяется с течением времени.

David Diaz 06.08.2020 19:52

@DavidDiaz В этой программе * Multiple Monitor Manager) теперь 2 577 строк, и она по-прежнему требует дополнительных функций. ET используется только в одном месте. Это верно для вашей точки зрения, как только импорт используется во втором месте, я вместо этого перемещаю его наверх.

WinEunuuchs2Unix 08.08.2020 03:49

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

Skeleton Bow 17.02.2021 02:26

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