Локальные операторы импорта в Python
Я думаю, что приведение оператора import близко к фрагменту, который использует его, помогает читаемости, делая свои зависимости более ясными. Будет ли Python кэшировать это? Мне все равно? Это плохая идея?
def Process():
import StringIO
file_handle=StringIO.StringIO('hello world')
#do more stuff
for i in xrange(10): Process()
Немного больше оправдания: это для методов, которые используют тайные биты библиотеки, но когда я реорганизую метод в другой файл, я не понимаю, что я пропустил внешнюю зависимость, пока не получаю ошибку времени выполнения.
Ответы
Ответ 1
Другие ответы проявляют легкую путаницу в отношении того, как import
действительно работает.
Это утверждение:
import foo
примерно эквивалентен этому утверждению:
foo = __import__('foo', globals(), locals(), [], -1)
То есть он создает переменную в текущей области с тем же именем, что и запрошенный модуль, и присваивает ей результат вызова __import__()
с этим именем модуля и лодкой аргументов по умолчанию.
Обработчик функции __import__()
концептуально преобразует строку ('foo'
) в объект модуля. Модули кэшируются в sys.modules
и что первое место __import__()
выглядит - если sys.modules имеет запись для 'foo'
, то что __import__('foo')
вернется, что бы это ни было. Это действительно не волнует тип. Вы можете видеть это в действии самостоятельно; попробуйте запустить следующий код:
import sys
sys.modules['boop'] = (1, 2, 3)
import boop
print boop
Оставляя в стороне стилистические проблемы на данный момент, наличие инструкции импорта внутри функции работает так, как вам хотелось бы. Если модуль ранее не импортировался, он импортируется и кэшируется в sys.modules. Затем он назначает модуль локальной переменной с этим именем. Он не изменяет никакого состояния на уровне модуля. Возможно, он изменит какое-то глобальное состояние (добавив новую запись в sys.modules).
Тем не менее, я почти никогда не использую import
внутри функции. Если при импорте модуля создается заметное замедление в вашей программе - например, он выполняет длительные вычисления в своей статической инициализации или просто массивный модуль, и ваша программа редко нуждается в модуле для чего угодно, совершенно нормально иметь импорт только внутри функции, в которых он использовался. (Если бы это было неприятно, Гвидо прыгнул бы в свою машину времени и изменил бы Python, чтобы помешать нам это сделать.) Но, как правило, я и общее сообщество Python ставили все наши операторы импорта в верхней части модуля в области модуля.
Ответ 2
См. PEP 8:
Импорт всегда помещается наверху файл, сразу после любого модуля комментарии и docstrings, а также перед модульными глобалями и константами.
Обратите внимание, что это чисто стилистический выбор, поскольку Python будет обрабатывать все операторы import
одинаково независимо от того, где они объявлены в исходном файле. Тем не менее я бы рекомендовал вам следовать обычной практике, так как это сделает ваш код более читаемым для других.
Ответ 3
Стиль в стороне, правда, импортированный модуль будет импортироваться только один раз (если reload
не вызван в указанном модуле). Однако каждый вызов import Foo
неявно проверяет, загружен ли этот модуль (проверив sys.modules
).
Рассмотрим также "дизассемблирование" двух других равных функций, в которых один пытается импортировать модуль, а другой - не:
>>> def Foo():
... import random
... return random.randint(1,100)
...
>>> dis.dis(Foo)
2 0 LOAD_CONST 1 (-1)
3 LOAD_CONST 0 (None)
6 IMPORT_NAME 0 (random)
9 STORE_FAST 0 (random)
3 12 LOAD_FAST 0 (random)
15 LOAD_ATTR 1 (randint)
18 LOAD_CONST 2 (1)
21 LOAD_CONST 3 (100)
24 CALL_FUNCTION 2
27 RETURN_VALUE
>>> def Bar():
... return random.randint(1,100)
...
>>> dis.dis(Bar)
2 0 LOAD_GLOBAL 0 (random)
3 LOAD_ATTR 1 (randint)
6 LOAD_CONST 1 (1)
9 LOAD_CONST 2 (100)
12 CALL_FUNCTION 2
15 RETURN_VALUE
Я не уверен, как больше байт-код будет переведен для виртуальной машины, но если это будет важным внутренним циклом для вашей программы, вы наверняка захотите добавить некоторый вес к подходу Bar
к Foo
.
Быстрый и грязный тест timeit
показывает небольшое улучшение скорости при использовании Bar
:
$ python -m timeit -s "from a import Foo,Bar" -n 200000 "Foo()"
200000 loops, best of 3: 10.3 usec per loop
$ python -m timeit -s "from a import Foo,Bar" -n 200000 "Bar()"
200000 loops, best of 3: 6.45 usec per loop
Ответ 4
Я сделал это, а потом пожалел, что нет. Обычно, если я пишу функцию, и эта функция должна использовать StringIO
, я могу посмотреть вверху модуля, посмотреть, импортируется ли он, а затем добавить его, если это не так.
Предположим, я этого не делаю; предположим, что я добавляю его локально внутри своей функции. А потом предположим, что в какой-то момент я или кто-то еще добавляет кучу других функций, которые используют StringIO
. Этот человек будет смотреть вверху модуля и добавить import StringIO
. Теперь ваша функция содержит код, который не только неожиданный, но и избыточный.
Кроме того, это нарушает то, что я считаю довольно важным принципом: прямо не изменяйте состояние уровня модуля внутри функции.
Edit:
Собственно, оказывается, что все вышеперечисленное - глупость.
Импорт модуля не изменяет состояние на уровне модуля (он инициализирует импортируемый модуль, если ничего еще не существует, но это совсем не то же самое). Импорт модуля, который вы уже импортировали в другое место, не требует ничего, кроме поиска sys.modules
и создания переменной в локальной области.
Зная это, я чувствую себя глупо, фиксируя все места в моем коде, где я его исправил, но чтобы мой крест нести.
Ответ 5
Когда интерпретатор Python попадает в оператор импорта, он начинает считывать все определения функций в импортируемом файле. Это объясняет, почему иногда импорт может занять некоторое время.
Идея делать все импорт в начале - это стилистическая конвенция, о которой указывает Эндрю Хейр. Однако вы должны иметь в виду, что, делая это, вы неявно делаете проверку интерпретатора, если этот файл уже был импортирован после первого его импорта. Это также становится проблемой, когда ваш файл кода становится большим, и вы хотите "обновить" свой код, чтобы удалить или заменить определенные зависимости. Это потребует от вас поискать весь файл кода, чтобы найти все места, где вы импортировали этот модуль.
Я бы предложил следующее соглашение и сохранить импорт в верхней части вашего файла кода. Если вы действительно хотите отслеживать зависимости для функций, я бы предложил добавить их в docstring для этой функции.
Ответ 6
Я вижу два способа, когда вам нужно импортировать его локально
-
Для целей тестирования или для временного использования вам нужно что-то импортировать, в этом случае вы должны поместить импорт в место использования.
-
Иногда, чтобы избежать циклической зависимости, вам нужно будет импортировать ее внутри функции, но это будет означать, что у вас есть проблема else где.
В противном случае всегда ставьте его сверху для эффективности и согласованности.