Що насправді робить __future__ import absol_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__твердження, здавалося б, не робить того, що воно говорить, і яке вирішення цього протиріччя між цими двома розділами документації, а також між описаною та реальною поведінкою?


Відповіді:


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.pyзмусити Python трактувати його як частину pkgпакету, навіть коли він працює як файл:

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

Однак це не вплине sys.path. Вам знадобиться деяка додаткова обробка для видалення pkgкаталогу з шляху, і якщо pkgбатьківський каталог не стоїть на шляху, вам потрібно буде також дотримуватися цього.


2
Гаразд, я маю на увазі, це я розумію. Саме таку поведінку моїй публікації документує. Однак, перед цим, два питання: (1.) Якщо "це не зовсім так", чому документи доказують категорично? і, (2.) Як ви зробите це, import stringякщо випадково затіните його, принаймні, не пробиваючи наскрізь sys.modules. Хіба це не те, що from __future__ import absolute_importпризначене для запобігання? Що це робить? (PS, я не переживаю.)
алхімік

14
Так, це я (суперечка "не корисний", не "неправильний"). З нижнього розділу зрозуміло, що ОП розуміє, як sys.pathпрацює, а власне питання взагалі не було вирішено. Тобто, що from __future__ import absolute_importнасправді робить?
Вім

5
@ Two-BitAlchemist: 1) Журнал змін є нечітко вираженим та ненормативним. 2) Ви перестаєте її відтіняти. Навіть простріл sys.modulesне допоможе отримати stringмодуль стандартної бібліотеки, якщо ви затінили його власним модулем верхнього рівня. from __future__ import absolute_importне призначено зупиняти модулі верхнього рівня від затінення модулів верхнього рівня; передбачається зупинити внутрішні модулі пакету від затінення модулів верхнього рівня. Якщо ви запускаєте файл як частину pkgпакету, внутрішні файли пакету перестають відображатися як найвищий рівень.
user2357112 підтримує Моніку

@ Two-BitAlchemist: Відповідь переглянути. Чи корисніша ця версія?
user2357112 підтримує Моніку

1
@storen: Припускаючи pkg, що це пакет на шляху пошуку імпорту, таким має бути python -m pkg.main. -mпотрібне ім'я модуля, а не шлях до файлу.
user2357112 підтримує Моніку

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списку і правильно обробляє семантику підмодуля.

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


Тож по суті це працює лише у вузькому випадку, коли ви уникли проблеми "поточного каталогу"? Це здається набагато слабшою реалізацією, ніж описано в 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це пакет.
Бакуріу
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.