Что делает из __future__ import absolute_import на самом деле?
У меня ответил вопрос об абсолютном импорте в 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 changelog, мы находим это (выделено мной):
В 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
будет всегда найти версию std-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
'd to pkg
и запускать импорт непосредственно из REPL. Ни один из этих вариантов (а также их комбинация) не изменили результаты выше. Я не могу смириться с тем, что я прочитал о директиве __future__
и абсолютном импорте.
Мне кажется, что это легко объясняется следующим (это из документов Python 2, но это утверждение остается неизменным в том же docs для Python 3):
sys.path
(...)
Как инициализировано при запуске программы, первым элементом этого списка, path[0]
, является каталог, содержащий script, который использовался для вызова интерпретатора Python. Если каталог script недоступен (например, если интерпретатор вызывается интерактивно или если script считывается со стандартного ввода), path[0]
- это пустая строка , которая направляет Python на поиск модулей в текущей сначала.
Так что мне не хватает? Почему оператор __future__
, по-видимому, не делает то, что он говорит, и какова резолюция этого противоречия между этими двумя разделами документации, а также между описанным и фактическим поведением?
Ответы
Ответ 1
Строка изменений неверно сформулирована. 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.py
, чтобы Python рассматривал его как часть пакета pkg
даже при запуске как файл:
if __name__ == '__main__' and __package__ is None:
__package__ = 'pkg'
Однако это не повлияет на sys.path
. Вам понадобится дополнительная обработка, чтобы удалить каталог pkg
из пути, и если pkg
родительский каталог не находится на пути, вам также нужно будет придерживаться этого пути.
Ответ 2
Разница между абсолютным и относительным импортом вступает в силу только тогда, когда вы импортируете модуль из пакета, и этот модуль импортирует другой подмодуль из этого пакета. Увидеть разницу:
$ 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
и правильно обрабатывает семантику подмодуля.
Кроме того, я предпочитаю использовать явный относительный импорт для подмодулей пакетов, так как они предоставляют больше семантики и улучшают сообщения об ошибках в случае сбоя.