Стиль кодирования импорта 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, если я не перейду к началу файла и не добавлю импорт.

Ответ 8

Я считаю, что это рекомендуемый подход в некоторых случаях/сценариях. Например, в Google App Engine рекомендуются ленивые загрузки больших модулей, так как это минимизирует затраты на прогрев создания новых Python VM/интерпретаторов. Посмотрите на презентацию Google Engineer, описывающую это. Однако имейте в виду, что это не означает означает, что вы должны lazy-load все ваши модули.

Ответ 9

Возможно, вы захотите взглянуть на импорт служебных инструкций в вики python. Короче: если модуль уже загружен (посмотрите sys.modules), ваш код будет работать медленнее. Если ваш модуль еще не загружен, и будет foo загружаться только при необходимости, что может быть нулевым, тогда общая производительность будет лучше.

Ответ 10

Реализации безопасности

Рассмотрим среду, в которой весь ваш код Python находится в папке, доступ к которой имеет только привилегированный пользователь. Чтобы не запускать всю вашу программу в качестве привилегированного пользователя, вы решаете отказаться от привилегий для непривилегированного пользователя во время выполнения. Как только вы примете функцию, которая импортирует другой модуль, ваша программа выкинет ImportError, так как непривилегированный пользователь не сможет импортировать модуль из-за прав доступа к файлам.