Операторы локального импорта в Python

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

def Process():
    import StringIO
    file_handle=StringIO.StringIO('hello world')
    #do more stuff

for i in xrange(10): Process()

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


person Dustin Getz    schedule 09.11.2009    source источник
comment
было бы очень неплохо иметь with (import StringIO) как moduleName: синтаксис   -  person JeremyKun    schedule 08.02.2012
comment
@Bean: если это так, что вам не нужно вводить длинное или неудобное имя модуля, то есть простой способ: import StringIO, а затем sio = StringIO. Теперь вы можете сделать file_handle = sio.StringIO('hello world') и сохранить эти драгоценные пять символов. Однако я бы использовал это экономно, потому что это может затруднить чтение кода (назначение легко пропустить; нестандартные имена модулей могут отвлекать).   -  person cvoinescu    schedule 03.05.2012


Ответы (6)


Другие ответы демонстрируют легкое замешательство относительно того, как на самом деле работает 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 помещали все наши операторы импорта вверху модуля в области модуля.

person Larry Hastings    schedule 11.11.2009
comment
это также иногда спасает вас от циклического импорта (например: если вам нужно импортировать модель в ваш файл manager.py с помощью django, а models.py уже импортирует файл manager.py... что обычно и происходит) - person Jiaaro; 27.05.2011
comment
Помимо четкого ответа, я рад, что вы использовали 3 не, так как 2 или 4 могут сбить с толку. - person matiasg; 03.12.2014

См. PEP 8:

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

Обратите внимание, что это чисто стилистический выбор, поскольку Python будет обрабатывать все операторы import одинаково, независимо от того, где они объявлены в исходном файле. Тем не менее, я бы порекомендовал вам следовать общепринятой практике, так как это сделает ваш код более читаемым для других.

person Andrew Hare    schedule 09.11.2009
comment
классная ссылка, но: две веские причины для нарушения определенного правила: (1) применение правила сделало бы код менее читаемым даже для тех, кто привык читать код, который следует правилам. ... - person Dustin Getz; 09.11.2009
comment
Это верно только для импорта верхнего уровня. Импорт внутри функции не будет обработан до тех пор, пока эта функция не будет вызвана. Хотя я обычно не рекомендую этого делать, это может быть полезно, когда у вас есть зависимость, которую либо очень дорого загружать, либо она может быть недоступна во всех средах. - person Christopher Barber; 12.06.2017

Помимо стиля, это правда, что импортированный модуль будет импортирован только один раз (если только в указанном модуле не вызывается 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
person Mark Rushakoff    schedule 09.11.2009

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

Предположим, я этого не делаю; предположим, я добавляю его локально в свою функцию. А затем предположим, что в какой-то момент я или кто-то другой добавил кучу других функций, использующих StringIO. Этот человек посмотрит на верхнюю часть модуля и добавит import StringIO. Теперь ваша функция содержит не только неожиданный, но и избыточный код.

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

Изменить:

На самом деле получается, что все вышеперечисленное — ерунда.

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

Зная это, я чувствую себя немного глупо, исправляю все места в моем коде, где я это исправил, но это мой крест, который я должен нести.

person Robert Rossney    schedule 09.11.2009
comment
я выделил это для вас жирным шрифтом, потому что я не думал об этом, и это действительно очевидно в ретроспективе. - person Dustin Getz; 10.11.2009
comment
Если под этим вы подразумеваете, что импорт StringIO в середине функции вообще что-либо меняет для области модуля, в которой была определена функция... это неправильно. Как я объясняю в своем ответе, выполнение импорта в середине функции ничего не меняет в состоянии модуля, в котором эта функция была определена. И что не так с изменением состояния на уровне модуля изнутри функции? Для этого есть целое ключевое слово: глобальный. - person Larry Hastings; 12.11.2009

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

Идея, стоящая за всем импортом в начале, ЯВЛЯЕТСЯ стилистической условностью, как указывает Эндрю Хэйр. Однако вы должны иметь в виду, что, делая это, вы неявно заставляете интерпретатор проверять, был ли этот файл уже импортирован после первого импорта. Это также становится проблемой, когда ваш файл кода становится большим, и вы хотите «обновить» свой код, чтобы удалить или заменить определенные зависимости. Это потребует от вас поиска по всему файлу кода, чтобы найти все места, где вы импортировали этот модуль.

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

person inspectorG4dget    schedule 09.11.2009
comment
Когда интерпретатор Python попадает в оператор импорта, он начинает читать все определения функций в файле — (1) это происходит только в ПЕРВЫЙ раз, когда модуль импортируется во время текущего запуска интерпретатора Python (и импортированный модуль спрятан в sys.modules, так что в следующий раз требуется только поиск имени (2) чтение определений функций - это НЕ то, что он делает; он ВЫПОЛНЯЕТ весь код на уровне модуля (в основном операторы def и class).Другие вещи, например, больше импорта, настройка модуля структуры данных уровня (иногда из файлов) могут занять некоторое время (3) не имеет значения. - person John Machin; 10.11.2009

Я вижу два способа, когда вам нужно импортировать его локально

  1. Для целей тестирования или для временного использования вам нужно что-то импортировать, в этом случае вы должны поместить импорт в место использования.

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

В противном случае всегда ставьте его на первое место ради эффективности и согласованности.

person Community    schedule 09.11.2009