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
pkg
zawierający podmodułypkg.main
ipkg.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 jakopkg.string
moduł i ten moduł zostanie powiązany z nazwą"string"
wpkg.main
przestrzeni nazw modułu.
Stworzyłem więc dokładną strukturę katalogów:
$ ls -R
.:
pkg/
./pkg:
__init__.py main.py string.py
__init__.py
i string.py
są puste. main.py
zawiera 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ć
import
zachowanie na bezwzględne importowanie za pomocąfrom __future__ import absolute_import
dyrektywy. To zachowanie importu absolutnego stanie się domyślnym w przyszłej wersji (prawdopodobnie Python 2.7). Gdy import bezwzględny jest domyślny,import string
zawsze znajdzie wersję biblioteki standardowej.
Stworzyłem w ten sposób pkg/main2.py
, identyczny, main.py
ale 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 string
bę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ę print
instrukcji 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.py
Ja stworzyłem pusty module - katalog o nazwie string
zawierającej tylko pusta __init__.py
- i zamiast wydawania importu z main.py
mam cd
„d, aby pkg
i 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?
źródło
Odpowiedzi:
Dziennik zmian jest niechlujnie sformułowany.
from __future__ import absolute_import
nie dba o to, czy coś jest częścią biblioteki standardowej iimport string
nie zawsze daje moduł biblioteki standardowej z włączonymi bezwzględnymi importami.from __future__ import absolute_import
oznacza, że jeśli tyimport string
, Python zawsze będzie szukałstring
modułu najwyższego poziomu , a niecurrent_package.string
. Jednak nie wpływa to na logikę używaną przez Python do decydowania, który plik jeststring
modułem. Kiedy to zrobiszpkg/script.py
nie wygląda jak część pakietu w Pythonie. Zgodnie z normalnymi proceduramipkg
katalog jest dodawany do ścieżki, a wszystkie.py
pliki wpkg
katalogu wyglądają jak moduły najwyższego poziomu.import string
znajdujepkg/string.py
nie dlatego, że wykonuje względny import, ale dlatego, żepkg/string.py
wygląda na moduł najwyższego poziomustring
. Fakt, że nie jest tostring
moduł biblioteki standardowej , nie pojawia się.Aby uruchomić plik jako część
pkg
pakietu, możesz to zrobićW takim przypadku
pkg
katalog nie zostanie dodany do ścieżki. Jednak bieżący katalog zostanie dodany do ścieżki.Możesz również dodać szablon do,
pkg/script.py
aby Python traktował go jako częśćpkg
pakietu, nawet gdy jest uruchamiany jako plik:Jednak to nie wpłynie
sys.path
. Będziesz potrzebować dodatkowej obsługi, aby usunąćpkg
katalog ze ścieżki, a jeślipkg
katalogu nadrzędnego nie ma na ścieżce, musisz go również umieścić na ścieżce.źródło
import string
jeśli przypadkowo ją prześlizgniesz, przynajmniej bez przeczesywaniasys.modules
. Czy to nie jest to, cofrom __future__ import absolute_import
ma zapobiegać? Co to robi? (PS, nie jestem zwolennikiem krytyki.)sys.path
działa, a rzeczywiste pytanie w ogóle nie zostało rozwiązane. To znaczy, cofrom __future__ import absolute_import
właściwie robi?sys.modules
nie dostanieszstring
modułu biblioteki standardowej , jeśli prześledzisz go swoim własnym modułem najwyższego poziomu.from __future__ import absolute_import
nie 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ęśćpkg
pakietu, wewnętrzne pliki pakietu przestaną być wyświetlane jako najwyższy poziom.pkg
że pakiet na ścieżce wyszukiwania importu powinien byćpython -m pkg.main
.-m
wymaga nazwy modułu, a nie ścieżki do pliku.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ę:
W szczególności:
Zauważ, że
python2 pkg/main2.py
ma inne zachowanie niż uruchamianie,python2
a następnie importowaniepkg.main2
(co jest równoważne użyciu-m
przełącznika).Jeśli kiedykolwiek zechcesz uruchomić podmoduł pakietu, zawsze używaj
-m
przełącznika, który uniemożliwia interpreterowi połączeniesys.path
listy 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.
źródło
python2 pkg/main2.py
zachowuje się inaczej niż uruchamianie python2, a następnie importowanie pkg.main2?pkg/main2.py
Python (wersja 2) nie traktujepkg
jako pakietu. Używającpython2 -m pkg.main2
lub importując go , weź pod uwagę, żepkg
jest to pakiet.