Co właściwie robi from __future__ import absolute_import?

164

I odpowiedział na pytanie dotyczące bezwzględnych importu w Pythonie, i pomyślałem, zrozumiałem, na podstawie odczytu z Python 2.5 changelog i towarzyszące PEP . Jednak po zainstalowaniu Pythona 2.5 i próbie stworzenia przykładu prawidłowego użycia from __future__ import absolute_import, zdaję sobie sprawę, że sprawy nie są takie jasne.

Prosto z dziennika zmian połączonego powyżej, to stwierdzenie dokładnie podsumowało moje rozumienie absolutnej zmiany importu:

Powiedzmy, że masz taki katalog pakietów:

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

Definiuje pakiet o nazwie pkgzawierający podmoduły pkg.maini pkg.string.

Rozważ kod w module main.py. Co się stanie, jeśli wykona instrukcję import string? W Pythonie 2.4 i wcześniejszych, najpierw zajrzy do katalogu pakietu, aby wykonać względny import, znajdzie pkg / string.py, zaimportuje zawartość tego pliku jako pkg.stringmoduł i ten moduł zostanie powiązany z nazwą "string"w pkg.mainprzestrzeni nazw modułu.

Stworzyłem więc dokładną strukturę katalogów:

$ ls -R
.:
pkg/

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

__init__.pyi string.pysą puste. main.pyzawiera następujący kod:

import string
print string.ascii_uppercase

Zgodnie z oczekiwaniami, uruchomienie tego w Pythonie 2.5 kończy się niepowodzeniem z 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'

Jednak dalej w dzienniku zmian 2.5 znajdujemy to (podkreślenie dodane):

W Pythonie 2.5 możesz przełączyć importzachowanie na bezwzględne importowanie za pomocą from __future__ import absolute_importdyrektywy. To zachowanie importu absolutnego stanie się domyślnym w przyszłej wersji (prawdopodobnie Python 2.7). Gdy import bezwzględny jest domyślny, import stringzawsze znajdzie wersję biblioteki standardowej.

Stworzyłem w ten sposób pkg/main2.py, identyczny, main.pyale z dodatkową przyszłą dyrektywą importową. Teraz wygląda to tak:

from __future__ import absolute_import
import string
print string.ascii_uppercase

Jednak uruchomienie tego w Pythonie 2.5 ... kończy się niepowodzeniem z 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'

To dość kategorycznie zaprzecza oświadczenie, że import stringbędzie zawsze znaleźć wersję STD-lib z włączonym import bezwzględne. Co więcej, pomimo ostrzeżenia, że ​​import absolutny ma stać się „nowym domyślnym” zachowaniem, napotkałem ten sam problem, używając zarówno Pythona 2.7, z __future__dyrektywą lub bez niej :

$ 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'

a także Python 3.5, z lub bez (zakładając zmianę printinstrukcji w obu plikach):

$ 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'

Przetestowałem inne odmiany tego. Zamiast string.pyJa stworzyłem pusty module - katalog o nazwie stringzawierającej tylko pusta __init__.py- i zamiast wydawania importu z main.pymam cd„d, aby pkgi uruchomić import bezpośrednio z REPL. Żadna z tych odmian (ani ich kombinacja) nie zmieniła powyższych wyników. Nie mogę tego pogodzić z tym, co przeczytałem o __future__dyrektywie i bezwzględnym imporcie.

Wydaje mi się, że można to łatwo wyjaśnić w następujący sposób (pochodzi z dokumentacji Pythona 2, ale ta instrukcja pozostaje niezmieniona w tych samych dokumentach dla Pythona 3):

sys.path

(...)

Zgodnie z inicjalizacją podczas uruchamiania programu, pierwszą pozycją na tej liście path[0]jest katalog zawierający skrypt, który został użyty do wywołania interpretera Pythona. Jeśli katalog skryptu nie jest dostępny (np. Jeśli interpreter jest wywoływany interaktywnie lub jeśli skrypt jest czytany ze standardowego wejścia), path[0]jest to pusty łańcuch, który kieruje Pythona do wyszukiwania modułów w bieżącym katalogu w pierwszej kolejności.

Więc czego mi brakuje? Dlaczego __future__stwierdzenie pozornie nie robi tego, co mówi, i jakie jest rozwiązanie tej sprzeczności między tymi dwoma rozdziałami dokumentacji, a także między opisanym a rzeczywistym zachowaniem?

Dwubitowy Alchemik
źródło

Odpowiedzi:

104

Dziennik zmian jest niechlujnie sformułowany. from __future__ import absolute_importnie dba o to, czy coś jest częścią biblioteki standardowej i import stringnie zawsze daje moduł biblioteki standardowej z włączonymi bezwzględnymi importami.

from __future__ import absolute_importoznacza, że ​​jeśli ty import string, Python zawsze będzie szukał stringmodułu najwyższego poziomu , a nie current_package.string. Jednak nie wpływa to na logikę używaną przez Python do decydowania, który plik jest stringmodułem. Kiedy to zrobisz

python pkg/script.py

pkg/script.pynie wygląda jak część pakietu w Pythonie. Zgodnie z normalnymi procedurami pkgkatalog jest dodawany do ścieżki, a wszystkie .pypliki w pkgkatalogu wyglądają jak moduły najwyższego poziomu. import stringznajduje pkg/string.pynie dlatego, że wykonuje względny import, ale dlatego, że pkg/string.pywygląda na moduł najwyższego poziomu string. Fakt, że nie jest to stringmoduł biblioteki standardowej , nie pojawia się.

Aby uruchomić plik jako część pkgpakietu, możesz to zrobić

python -m pkg.script

W takim przypadku pkgkatalog nie zostanie dodany do ścieżki. Jednak bieżący katalog zostanie dodany do ścieżki.

Możesz również dodać szablon do, pkg/script.pyaby Python traktował go jako część pkgpakietu, nawet gdy jest uruchamiany jako plik:

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

Jednak to nie wpłynie sys.path. Będziesz potrzebować dodatkowej obsługi, aby usunąć pkgkatalog ze ścieżki, a jeśli pkgkatalogu nadrzędnego nie ma na ścieżce, musisz go również umieścić na ścieżce.

user2357112 obsługuje Monikę
źródło
2
OK, rozumiem. Dokładnie takie zachowanie dokumentuje mój post. Wobec tego jednak dwa pytania: (1.) Jeśli „to nie do końca prawda”, dlaczego doktorzy kategorycznie twierdzą, że tak jest? i, (2.) Jak więc możesz to zrobić, import stringjeśli przypadkowo ją prześlizgniesz, przynajmniej bez przeczesywania sys.modules. Czy to nie jest to, co from __future__ import absolute_importma zapobiegać? Co to robi? (PS, nie jestem zwolennikiem krytyki.)
Two-Bit Alchemist
14
Tak, to byłem ja (głos przeciw „nieprzydatny”, nie „zły”). Z dolnej sekcji jasno wynika, że ​​PO rozumie, jak sys.pathdziała, a rzeczywiste pytanie w ogóle nie zostało rozwiązane. To znaczy, co from __future__ import absolute_importwłaściwie robi?
wim
5
@ Two-BitAlchemist: 1) Dziennik zmian jest luźno sformułowany i nienormatywny. 2) Przestajesz to śledzić. Nawet przeglądanie sys.modulesnie dostaniesz stringmodułu biblioteki standardowej , jeśli prześledzisz go swoim własnym modułem najwyższego poziomu. from __future__ import absolute_importnie ma powstrzymywać modułów najwyższego poziomu przed przesłanianiem modułów najwyższego poziomu; ma powstrzymać wewnętrzne moduły przed zacienianiem modułów najwyższego poziomu. Jeśli uruchomisz plik jako część pkgpakietu, wewnętrzne pliki pakietu przestaną być wyświetlane jako najwyższy poziom.
user2357112 obsługuje Monikę
@ Two-BitAlchemist: poprawiono odpowiedź. Czy ta wersja jest bardziej pomocna?
user2357112 obsługuje Monikę
1
@storen: Zakładając, pkgże pakiet na ścieżce wyszukiwania importu powinien być python -m pkg.main. -mwymaga nazwy modułu, a nie ścieżki do pliku.
user2357112 obsługuje Monikę
44

Różnica między importem bezwzględnym i względnym pojawia się tylko wtedy, gdy importujesz moduł z pakietu, a ten moduł importuje inny podmoduł z tego pakietu. Zobacz różnicę:

$ 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
>>> 

W szczególności:

$ 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

Zauważ, że python2 pkg/main2.pyma inne zachowanie niż uruchamianie, python2a następnie importowanie pkg.main2(co jest równoważne użyciu -mprzełącznika).

Jeśli kiedykolwiek zechcesz uruchomić podmoduł pakietu, zawsze używaj -mprzełącznika, który uniemożliwia interpreterowi połączenie sys.pathlisty i poprawnie obsługuje semantykę modułu podrzędnego.

Ponadto wolę używać jawnych importów względnych dla podmodułów pakietów, ponieważ zapewniają one większą semantykę i lepsze komunikaty o błędach w przypadku awarii.

Bakuriu
źródło
Więc zasadniczo działa to tylko w wąskim przypadku, w którym uniknąłeś problemu z „bieżącym katalogiem”? Wydaje się, że jest to znacznie słabsza implementacja niż opisana w PEP 328 i dzienniku zmian 2.5. Czy uważasz, że dokumentacja jest niedokładna?
Two-Bit Alchemist
@ Two-BitAlchemist Właściwie to , co robisz, to „wąska sprawa”. Uruchamiasz tylko jeden plik Pythona do wykonania, ale może to spowodować setki importów. Podmoduły pakietu po prostu nie powinny być wykonywane, to wszystko.
Bakuriu
dlaczego python2 pkg/main2.pyzachowuje się inaczej niż uruchamianie python2, a następnie importowanie pkg.main2?
storen
1
@storen Dzieje się tak, ponieważ zmienia się zachowanie ze względnymi importami. Po uruchomieniu pkg/main2.pyPython (wersja 2) nie traktuje pkgjako pakietu. Używając python2 -m pkg.main2lub importując go , weź pod uwagę, że pkgjest to pakiet.
Bakuriu,