Стиль кодирования импорта Python
Я обнаружил новый шаблон. Является ли эта модель хорошо известной или каково ее мнение?
В принципе, у меня есть трудное время, очищая вверх и вниз исходные файлы, чтобы выяснить, какие модули импортируются и т.д., поэтому теперь вместо
import foo
from bar.baz import quux
def myFunction():
foo.this.that(quux)
Я перемещаю весь свой импорт в функцию, в которой они используются, например:
def myFunction():
import foo
from bar.baz import quux
foo.this.that(quux)
Это делает несколько вещей. Во-первых, я редко случайно загрязняю свои модули содержимым других модулей. Я мог бы установить переменную __all__
для модуля, но тогда мне придется обновлять ее по мере развития модуля, и это не помогает загрязнению пространства имен для кода, который действительно живет в модуле.
Во-вторых, я редко получаю список импорта в верхней части моих модулей, половина или более которых мне больше не нужна, потому что я реорганизовал ее. Наконец, я нахожу этот шаблон намного проще для чтения, так как каждое упоминаемое имя находится прямо в теле функции.
Ответы
Ответ 1
Ответ на предыдущий вопрос по этому вопросу хорошо отформатирован, но абсолютно ошибочен в отношении производительности. Позвольте мне продемонстрировать
Производительность
Верхний импорт
import random
def f():
L = []
for i in xrange(1000):
L.append(random.random())
for i in xrange(1000):
f()
$ time python import.py
real 0m0.721s
user 0m0.412s
sys 0m0.020s
Импорт в тело функции
def f():
import random
L = []
for i in xrange(1000):
L.append(random.random())
for i in xrange(1000):
f()
$ time python import2.py
real 0m0.661s
user 0m0.404s
sys 0m0.008s
Как вы можете видеть, эффективнее импортировать модуль в функцию больше. Причина этого проста. Он перемещает ссылку из глобальной ссылки на локальную ссылку. Это означает, что для CPython, по крайней мере, компилятор будет генерировать команды LOAD_FAST
вместо инструкций LOAD_GLOBAL
. Это, как следует из названия, быстрее. Другой ответчик искусственно завысил производительность, попав в sys.modules
, импортируя на каждую итерацию цикла.
Как правило, лучше всего импортировать сверху, но производительность не является причиной, если вы много раз обращаетесь к модулю. Причины этого в том, что можно отслеживать, что модуль зависит от более легко, и что это согласуется с большинством остальных юнитов Python.
Ответ 2
У этого есть несколько недостатков.
Тестирование
В случае случайности вы хотите протестировать свой модуль с помощью модификации времени выполнения, это может усложнить задачу. Вместо того, чтобы делать
import mymodule
mymodule.othermodule = module_stub
Вам нужно будет
import othermodule
othermodule.foo = foo_stub
Это означает, что вам придется исправлять другой модуль по всему миру, а не просто изменять то, на что указывает ссылка в mymodule.
Отслеживание зависимостей
Это делает неочевидным, от каких модулей зависит ваш модуль. Это особенно раздражает, если вы используете много сторонних библиотек или повторно организуете код.
Мне нужно было сохранить какой-то старый код, который использовал импорт, встроенный повсюду, это сделало код чрезвычайно сложным для реорганизации или переупаковки.
Примечания о производительности
Из-за того, как пионы кэшируют модули, нет производительности. Фактически, поскольку модуль находится в локальном пространстве имен, есть небольшое преимущество в производительности для импорта модулей в функцию.
Верхний импорт
import random
def f():
L = []
for i in xrange(1000):
L.append(random.random())
for i in xrange(10000):
f()
$ time python test.py
real 0m1.569s
user 0m1.560s
sys 0m0.010s
Импорт в тело функции
def f():
import random
L = []
for i in xrange(1000):
L.append(random.random())
for i in xrange(10000):
f()
$ time python test2.py
real 0m1.385s
user 0m1.380s
sys 0m0.000s
Ответ 3
Несколько проблем с этим подходом:
- Это не сразу становится очевидным при открытии файла, на котором он зависит.
- Это запутает программы, которые должны анализировать зависимости, такие как
py2exe
, py2app
и т.д.
- Как насчет модулей, которые вы используете во многих функциях? Вы либо закончите с большим количеством избыточных вводок, либо вам придется иметь некоторые в верхней части файла и некоторые внутренние функции.
Итак... предпочтительный способ - разместить весь импорт в верхней части файла. Я обнаружил, что, если мне трудно отслеживать импорт, обычно это означает, что у меня слишком много кода, и мне лучше было бы разделить его на два или более файла.
Некоторые ситуации, когда я нашел импорт внутри функций полезными:
- Чтобы справиться с круговыми зависимостями (если вы действительно не можете их избежать)
- Специфичный для платформы код
Также: размещение импорта внутри каждой функции на самом деле не заметно медленнее, чем в верхней части файла. При первом загрузке каждого модуля он помещается в sys.modules
, и каждый последующий импорт требует только времени для поиска модуля, который довольно быстр (он не перезагружается).
Ответ 4
Еще одна полезная вещь: синтаксис from module import *
внутри функции был удален в Python 3.0.
Кратко об этом упоминается в разделе "Удаленный синтаксис" здесь:
http://docs.python.org/3.0/whatsnew/3.0.html
Ответ 5
Я бы предположил, что вы пытаетесь избежать импорта from foo import bar
. Я использую их только внутри пакетов, где разделение на модули является деталью реализации, и их все равно не будет.
Во всех других местах, где вы импортируете пакет, просто используйте import foo
, а затем укажите его полным именем foo.bar
. Таким образом, вы всегда можете указать, откуда приходит определенный элемент, и не нужно поддерживать список импортированных элементов (на самом деле это всегда будет устаревшим и импортировать больше не используемые элементы).
Если foo
- действительно длинное имя, вы можете упростить его с помощью import foo as f
, а затем написать f.bar
. Это все еще гораздо удобнее и яснее, чем сохранение всего импорта from
.
Ответ 6
Люди очень хорошо объяснили, почему избежать inline-импорта, но не на самом деле альтернативные рабочие процессы для устранения причин, по которым вы хотите их в первую очередь.
У меня есть трудное время, очищая вверх и вниз исходные файлы, чтобы выяснить, какие импорты модулей доступны и т.д.
Чтобы проверить неиспользуемые импорты, я использую pylint. Это статический (ish) -анализ кода Python, и одна из (многих) вещей, которую он проверяет, - это неиспользуемые импорты. Например, следующий script..
import urllib
import urllib2
urllib.urlopen("http://stackoverflow.com")
.. создаст следующее сообщение:
example.py:2 [W0611] Unused import urllib2
Что касается проверки доступных импортов, я обычно полагаюсь на TextMate (довольно упрощенное) завершение - когда вы нажимаете Esc, он завершает текущее слово с другими в документе. Если я сделал import urllib
, urll[Esc]
будет расширяться до urllib
, если я не перейду к началу файла и не добавлю импорт.
Ответ 7
С точки зрения производительности вы можете увидеть это: Должны ли операторы импорта Python всегда находиться в верхней части модуля?
В общем, я использую только локальный импорт, чтобы разорвать циклы зависимостей.
Ответ 8
Я считаю, что это рекомендуемый подход в некоторых случаях/сценариях. Например, в Google App Engine рекомендуются ленивые загрузки больших модулей, так как это минимизирует затраты на прогрев создания новых Python VM/интерпретаторов. Посмотрите на презентацию Google Engineer, описывающую это. Однако имейте в виду, что это не означает означает, что вы должны lazy-load все ваши модули.
Ответ 9
Возможно, вы захотите взглянуть на импорт служебных инструкций в вики python. Короче: если модуль уже загружен (посмотрите sys.modules
), ваш код будет работать медленнее. Если ваш модуль еще не загружен, и будет foo
загружаться только при необходимости, что может быть нулевым, тогда общая производительность будет лучше.
Ответ 10
Реализации безопасности
Рассмотрим среду, в которой весь ваш код Python находится в папке, доступ к которой имеет только привилегированный пользователь. Чтобы не запускать всю вашу программу в качестве привилегированного пользователя, вы решаете отказаться от привилегий для непривилегированного пользователя во время выполнения. Как только вы примете функцию, которая импортирует другой модуль, ваша программа выкинет ImportError
, так как непривилегированный пользователь не сможет импортировать модуль из-за прав доступа к файлам.