Что на самом деле делает __future__ импорт absolute_import?

164

Я ответил на вопрос об абсолютном импорте в Python, который, как мне показалось, я понял, прочитав журнал изменений Python 2.5 и сопровождающий его PEP . Однако после установки Python 2.5 и попытки создать пример правильного использования from __future__ import absolute_importя понимаю, что все не так ясно.

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

Допустим, у вас есть каталог пакетов, подобный этому:

pkg/
pkg/__init__.py
pkg/main.py
pkg/string.py

Это определяет пакет с именем , pkgсодержащей pkg.mainи pkg.stringподмодули.

Рассмотрим код в модуле main.py. Что произойдет, если он выполнит оператор import string? В Python 2.4 и более ранних версиях он сначала просматривает каталог пакета для выполнения относительного импорта, находит pkg / string.py, импортирует содержимое этого файла как pkg.stringмодуль, и этот модуль привязывается к имени "string"в pkg.mainпространстве имен модуля.

Итак, я создал эту точную структуру каталогов

$ ls -R
.:
pkg/

./pkg:
__init__.py  main.py  string.py

__init__.pyи string.pyпусты. main.pyсодержит следующий код:

import string
print string.ascii_uppercase

Как и ожидалось, запуск этого с Python 2.5 завершится неудачно с AttributeError:

$ python2.5 pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 2, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

Однако далее в журнале изменений 2.5 мы находим это (выделение добавлено):

В Python 2.5 вы можете переключать importповедение на абсолютный импорт, используя from __future__ import absolute_importдирективу. Такое поведение абсолютного импорта станет будущим по умолчанию в будущей версии (возможно, Python 2.7). Как только абсолютный импорт по умолчанию, import stringвсегда найдет версию стандартной библиотеки.

Таким образом, я создал pkg/main2.py, идентичный, main.pyно с дополнительной директивой импорта в будущем. Теперь это выглядит так:

from __future__ import absolute_import
import string
print string.ascii_uppercase

Запуск этого с Python 2.5, однако ... не удается с AttributeError:

$ python2.5 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 3, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

Это довольно категорически противоречит тому , что import stringбудет всегда найти версию станд-LIB с поддержкой абсолютного импорта. Более того, несмотря на предупреждение о том, что абсолютный импорт должен стать «новым режимом по умолчанию», я столкнулся с той же проблемой, используя оба языка Python 2.7, с __future__директивой или без нее:

$ python2.7 pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 2, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

$ python2.7 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 3, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

а также Python 3.5, с или без (при условии, что printоператор изменяется в обоих файлах):

$ python3.5 pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 2, in <module>
    print(string.ascii_uppercase)
AttributeError: module 'string' has no attribute 'ascii_uppercase'

$ python3.5 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 3, in <module>
    print(string.ascii_uppercase)
AttributeError: module 'string' has no attribute 'ascii_uppercase'

Я проверил другие варианты этого. Вместо этого string.pyя создал пустой модуль - каталог с именем, stringсодержащим только пустой __init__.py- и вместо того, чтобы выдавать импорт из main.py, я должен cdбыл pkgвыполнять импорт непосредственно из REPL. Ни один из этих вариантов (ни их комбинация) не изменил результаты выше. Я не могу совместить это с тем, что я прочитал о __future__директиве и абсолютном импорте.

Мне кажется, что это легко объяснить следующим (это из документов Python 2, но это утверждение остается неизменным в тех же документах для Python 3):

sys.path

(...)

Как инициализируется при запуске программы, первым элементом этого списка path[0]является каталог, содержащий скрипт, который использовался для вызова интерпретатора Python. Если каталог скриптов недоступен (например, если интерпретатор вызывается в интерактивном режиме или если скрипт читается из стандартного ввода), path[0]это пустая строка, которая направляет Python для поиска модулей в текущем каталоге в первую очередь.

Так чего мне не хватает? Почему __future__утверждение, по-видимому, не соответствует тому, что оно говорит, и каково разрешение этого противоречия между этими двумя разделами документации, а также между описанным и фактическим поведением?

Двухразрядный Алхимик
источник
Смотрите также: docs.python.org/2.5/whatsnew/pep-328.html
dreftymac

Ответы:

104

Список изменений небрежно сформулирован. from __future__ import absolute_importне заботится о том, является ли что-то частью стандартной библиотеки, и import stringне всегда дает вам модуль стандартной библиотеки с абсолютным импортом.

from __future__ import absolute_importозначает, что если вы import string, Python всегда будет искать stringмодуль верхнего уровня , а не current_package.string. Однако это не влияет на логику, которую Python использует, чтобы решить, какой файл является stringмодулем. Когда вы делаете

python pkg/script.py

pkg/script.pyне выглядит как часть пакета для Python. Следуя обычным процедурам, pkgкаталог добавляется в путь, и все .pyфайлы в pkgкаталоге выглядят как модули верхнего уровня. import stringнаходит pkg/string.pyне потому, что выполняет относительный импорт, а потому, что pkg/string.pyкажется модулем верхнего уровня string. Тот факт, что это не stringмодуль стандартной библиотеки , не подходит.

Чтобы запустить файл как часть pkgпакета, вы можете сделать

python -m pkg.script

В этом случае pkgкаталог не будет добавлен к пути. Тем не менее, текущий каталог будет добавлен в путь.

Вы также можете добавить шаблон, чтобы pkg/script.pyPython рассматривал его как часть pkgпакета, даже если он запускается как файл:

if __name__ == '__main__' and __package__ is None:
    __package__ = 'pkg'

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

user2357112 поддерживает Monica
источник
2
Хорошо, я имею в виду, я понимаю. Это именно то поведение, которое документирует мой пост. Перед лицом этого, однако, два вопроса: (1.) Если «это не совсем верно», почему документы категорически говорят, что это так? и, (2.) Как, import stringесли вы случайно замаскируете его, по крайней мере, не пробираясь сквозь него sys.modules. Разве это не то, что from __future__ import absolute_importпредназначено для предотвращения? Что оно делает? (PS, я не downvoter.)
Алхимик
14
Да, это был я (понижающий голос за «бесполезный», не за «неправильный»). Из нижнего раздела ясно, что ОП понимает, как sys.pathработает, а сам вопрос вообще не был рассмотрен. То есть, что на from __future__ import absolute_importсамом деле делает?
Вим
5
@ Two-BitAlchemist: 1) Журнал изменений является свободно сформулированным и ненормативным. 2) Ты перестаешь это скрывать. Даже пролистывание sys.modulesне даст вам stringмодуль стандартной библиотеки, если вы замаскируете его собственным модулем верхнего уровня. from __future__ import absolute_importне предназначен для того, чтобы блокировать модули верхнего уровня от теневого копирования модулей верхнего уровня; он должен помешать внутренним модулям пакетов скрывать модули верхнего уровня. Если вы запустите файл как часть pkgпакета, внутренние файлы пакета перестанут отображаться как верхний уровень.
user2357112 поддерживает Monica
@ Two-BitAlchemist: Ответ пересмотрен. Эта версия более полезна?
user2357112 поддерживает Monica
1
@storen: Предполагается, pkgчто это пакет в пути поиска импорта, который должен быть python -m pkg.main. -mнужно имя модуля, а не путь к файлу.
user2357112 поддерживает Monica
44

Разница между абсолютным и относительным импортом вступает в силу только тогда, когда вы импортируете модуль из пакета, и этот модуль импортирует другой субмодуль из этого пакета. Увидеть разницу:

$ mkdir pkg
$ touch pkg/__init__.py
$ touch pkg/string.py
$ echo 'import string;print(string.ascii_uppercase)' > pkg/main1.py
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pkg/main1.py", line 1, in <module>
    import string;print(string.ascii_uppercase)
AttributeError: 'module' object has no attribute 'ascii_uppercase'
>>> 
$ echo 'from __future__ import absolute_import;import string;print(string.ascii_uppercase)' > pkg/main2.py
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ
>>> 

В частности:

$ python2 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 1, in <module>
    from __future__ import absolute_import;import string;print(string.ascii_uppercase)
AttributeError: 'module' object has no attribute 'ascii_uppercase'
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ
>>> 
$ python2 -m pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ

Обратите внимание, что python2 pkg/main2.pyповедение отличается от запуска, python2а затем импорта pkg.main2(что эквивалентно использованию -mпереключателя).

Если вы когда-нибудь захотите запустить подмодуль пакета, всегда используйте -mпереключатель, который запрещает интерпретатору объединять цепочки в sys.pathсписок и правильно обрабатывает семантику подмодуля.

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

Bakuriu
источник
По сути, это работает только для узкого случая, когда вы избежали проблемы «текущего каталога»? Это, кажется, намного более слабая реализация, чем описано в PEP 328 и журнале изменений 2.5. Вы считаете документацию неточной?
Двухразрядный алхимик
@ Two-BitAlchemist На самом деле то, что вы делаете, это "узкий случай". Вы запускаете только один файл Python для выполнения, но это может вызвать сотни импортов. Подмодули пакета просто не должны выполняться, вот и все.
Бакуриу
почему python2 pkg/main2.pyповедение отличается от запуска python2 и импорта pkg.main2?
Storen
1
@storen Это потому, что меняется поведение с относительным импортом. При запуске pkg/main2.pypython (версия 2) не рассматривается pkgкак пакет. При использовании python2 -m pkg.main2или импортировать его делать принять во внимание , что pkgэто пакет.
Бакуриу