Skrypt vs. Moduł
Oto wyjaśnienie. Krótka wersja jest taka, że istnieje duża różnica między bezpośrednim uruchomieniem pliku Python a importowaniem tego pliku z innego miejsca. Sama wiedza o tym, w którym katalogu znajduje się plik, nie decyduje o tym, w którym pakiecie Python myśli, że jest. Zależy to dodatkowo od sposobu załadowania pliku do Pythona (przez uruchomienie lub zaimportowanie).
Istnieją dwa sposoby ładowania pliku Python: jako skrypt najwyższego poziomu lub jako moduł. Plik jest ładowany jako skrypt najwyższego poziomu, jeśli wykonasz go bezpośrednio, na przykład wpisując python myfile.py
w wierszu poleceń. Jest ładowany jako moduł, jeśli tak python -m myfile
, lub jeśli jest ładowany, gdy import
instrukcja napotkana jest w innym pliku. Jednocześnie może istnieć tylko jeden skrypt najwyższego poziomu; skrypt najwyższego poziomu to plik Python, który uruchomiłeś, aby rozpocząć.
Nazewnictwo
Po załadowaniu plik otrzymuje nazwę (zapisaną w __name__
atrybucie). Jeśli został załadowany jako skrypt najwyższego poziomu, jego nazwa to __main__
. Jeśli został załadowany jako moduł, jego nazwą jest nazwa pliku, poprzedzona nazwami dowolnych pakietów / podpakietów, których jest częścią, oddzielonych kropkami.
Na przykład w twoim przykładzie:
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleA.py
jeśli zaimportowałeś moduleX
(uwaga: zaimportowany , a nie bezpośrednio wykonany), jego nazwą byłoby package.subpackage1.moduleX
. Jeśli zaimportujesz moduleA
, jego nazwa to package.moduleA
. Jeśli jednak uruchomisz bezpośrednio moduleX
z wiersza polecenia, jego nazwa będzie zamiast tego __main__
, a jeśli bezpośrednio uruchomisz moduleA
z wiersza polecenia, będzie to nazwa __main__
. Kiedy moduł jest uruchamiany jako skrypt najwyższego poziomu, traci swoją normalną nazwę, a zamiast tego ma swoją nazwę __main__
.
Dostęp do modułu NIE poprzez jego pakiet zawierający
Występuje dodatkowe pomarszczenie: nazwa modułu zależy od tego, czy został zaimportowany „bezpośrednio” z katalogu, w którym się znajduje, czy zaimportowany za pośrednictwem pakietu. Ma to znaczenie tylko wtedy, gdy uruchomisz Python w katalogu i spróbujesz zaimportować plik do tego samego katalogu (lub jego podkatalogu). Na przykład, jeśli uruchomisz interpreter języka Python w katalogu, package/subpackage1
a następnie zrobisz import moduleX
, nazwa po moduleX
prostu będzie moduleX
, a nie package.subpackage1.moduleX
. Wynika to z faktu, że Python dodaje bieżący katalog do ścieżki wyszukiwania podczas uruchamiania; jeśli znajdzie moduł do zaimportowania w bieżącym katalogu, nie będzie wiedział, że ten katalog jest częścią pakietu, a informacje o pakiecie nie staną się częścią nazwy modułu.
Szczególnym przypadkiem jest interakcyjne uruchomienie interpretera (np. Po prostu wpisz python
i zacznij wpisywać kod Pythona w locie). W tym przypadku nazwa tej interaktywnej sesji to __main__
.
Oto kluczowa kwestia dla komunikatu o błędzie: jeśli nazwa modułu nie zawiera kropek, nie jest uważana za część pakietu . Nie ma znaczenia, gdzie plik faktycznie znajduje się na dysku. Liczy się tylko to, jak się nazywa, a nazwa zależy od sposobu załadowania.
Teraz spójrz na cytat zawarty w pytaniu:
Względne importy używają atrybutu nazwy modułu do ustalenia pozycji tego modułu w hierarchii pakietów. Jeśli nazwa modułu nie zawiera żadnych informacji o pakiecie (np. Jest ustawiona na „main”), importy względne są rozstrzygane tak, jakby moduł był modułem najwyższego poziomu, niezależnie od tego, gdzie moduł faktycznie znajduje się w systemie plików.
Względny import ...
Import względne użyć modułu za nazwę , aby określić, gdzie znajduje się w opakowaniu. Gdy korzystasz z importu względnego from .. import foo
, kropki wskazują na zwiększenie pewnej liczby poziomów w hierarchii pakietów. Na przykład, jeśli nazwa twojego bieżącego modułu to package.subpackage1.moduleX
, to ..moduleA
znaczy package.moduleA
. Aby a from .. import
działał, nazwa modułu musi zawierać co najmniej tyle kropek, ile jest w import
instrukcji.
... są tylko względne w pakiecie
Jednak jeśli nazwa twojego modułu to __main__
, nie jest uważany za pakiet. Jego nazwa nie zawiera kropek, dlatego nie można w nim używać from .. import
instrukcji. Jeśli spróbujesz to zrobić, pojawi się błąd „Względny import w pakiecie innym niż pakiet”.
Skrypty nie mogą importować względnych
To, co prawdopodobnie zrobiłeś, to próba uruchomienia moduleX
itp. Z wiersza poleceń. Gdy to zrobisz, jego nazwa zostanie ustawiona na __main__
, co oznacza, że względny import w nim nie powiedzie się, ponieważ jego nazwa nie ujawnia, że jest w pakiecie. Zauważ, że tak się stanie również, jeśli uruchomisz Python z tego samego katalogu, w którym znajduje się moduł, a następnie spróbujesz zaimportować ten moduł, ponieważ, jak opisano powyżej, Python znajdzie moduł w bieżącym katalogu „zbyt wcześnie”, nie zdając sobie sprawy, że jest część pakietu.
Pamiętaj również, że po uruchomieniu interaktywnego tłumacza „nazwa” tej interaktywnej sesji jest zawsze taka sama __main__
. Dlatego nie można wykonywać importów względnych bezpośrednio z sesji interaktywnej . Importów względnych można używać tylko w plikach modułów.
Dwa rozwiązania:
Jeśli naprawdę chcesz uruchomić moduleX
bezpośrednio, ale nadal chcesz, aby był traktowany jako część pakietu, możesz to zrobić python -m package.subpackage1.moduleX
. -m
Mówi Python ładować go jako moduł, a nie jak w skrypcie najwyższego poziomu.
A może tak naprawdę nie chcesz uruchomić moduleX
, po prostu chcesz uruchomić inny skrypt, powiedzmy myfile.py
, który używa funkcji w środku moduleX
. W takim przypadku umieść myfile.py
gdzie indziej - nie w package
katalogu - i uruchom go. Jeśli w środku myfile.py
robisz takie rzeczy from package.moduleA import spam
, będzie dobrze.
Notatki
Dla każdego z tych rozwiązań katalog pakietu ( package
w twoim przykładzie) musi być dostępny ze ścieżki wyszukiwania modułu Pythona ( sys.path
). Jeśli tak się nie stanie, nie będziesz w stanie używać niczego w pakiecie.
Od wersji Python 2.6 „nazwa” modułu do celów rozwiązywania pakietów jest określana nie tylko przez jego __name__
atrybuty, ale także przez __package__
atrybut. Dlatego unikam używania jawnego symbolu __name__
w odniesieniu do „nazwy” modułu. Od wersji Python 2.6 „nazwa” modułu jest efektywna __package__ + '.' + __name__
lub tylko __name__
jeśli __package__
tak None
.)