Chcę zaimportować funkcję z innego pliku w tym samym katalogu.
Czasami działa dla mnie, from .mymodule import myfunction
ale czasami dostaję:
SystemError: Parent module '' not loaded, cannot perform relative import
Czasami to działa from mymodule import myfunction
, ale czasami dostaję również:
SystemError: Parent module '' not loaded, cannot perform relative import
Nie rozumiem tutaj logiki i nie mogłem znaleźć żadnego wyjaśnienia. To wygląda zupełnie losowo.
Czy ktoś mógłby mi wyjaśnić, jaka jest logika tego wszystkiego?
python
python-3.x
python-import
John Smith Opcjonalnie
źródło
źródło
Odpowiedzi:
Układ taki jak ten jest dość powszechny ...
... z
mymodule.py
podobnym ......
myothermodule.py
jak ten ...... i
main.py
tym podobne ...... który działa dobrze po uruchomieniu
main.py
lubmypackage/mymodule.py
, ale nie działamypackage/myothermodule.py
, ze względu na względny import ...Sposób, w jaki powinieneś to uruchomić, to ...
... ale jest nieco gadatliwy i nie pasuje dobrze do linii shebang
#!/usr/bin/env python3
.Najprostszym rozwiązaniem w tym przypadku, zakładając, że nazwa
mymodule
jest globalnie unikalna, byłoby uniknięcie względnego importu i użycie ...... chociaż jeśli nie jest unikalny lub struktura pakietu jest bardziej złożona, musisz dołączyć katalog zawierający katalog pakietu
PYTHONPATH
i zrobić to w ten sposób ...... lub jeśli chcesz, aby działał „od razu po wyjęciu z pudełka”, możesz najpierw przeforsować
PYTHONPATH
kod w tym ...To rodzaj bólu, ale istnieje wskazówka, dlaczego w wiadomości e-mail napisanej przez pewnego Guido van Rossuma ...
Niezależnie od tego, czy uruchamianie skryptów w pakiecie jest antypatriorytetem, czy nie, jest ono subiektywne, ale osobiście uważam, że jest to bardzo przydatne w pakiecie, który mam, który zawiera niestandardowe widżety wxPython, dzięki czemu mogę uruchomić skrypt dla dowolnego pliku źródłowego, aby wyświetlić
wx.Frame
tylko zawierający ten widget do celów testowych.źródło
os.path.realpath(os.path.dirname(inspect.getfile(inspect.currentframe())))
jakbyś był pewny, że Twój moduł ma zawsze odpowiednie właściwościfile
, których możesz również użyćos.path.realpath(os.path.dirname(__file__))
.sys.path.append( os.path.join( os.path.dirname(__file__), os.path.pardir ) )
...which I've always seen as an antipattern.
Nie rozumiem, jak to jest anty-wzór ... Wydaje się, że byłoby bardzo wygodne, gdyby importowanie względne działało intuicyjnie. Chcę tylko móc importować rzeczy, które, jak wiem, znajdują się w tym samym katalogu. Zastanawiam się, jakie było jego rozumowanieWyjaśnienie
Od PEP 328
W pewnym momencie PEP 338 był w konflikcie z PEP 328 :
Aby rozwiązać ten problem, PEP 366 wprowadził zmienną najwyższego poziomu
__package__
:(moje podkreślenie)
Jeśli
__name__
jest'__main__'
,__name__.rpartition('.')[0]
zwraca pusty ciąg. Oto dlaczego w opisie błędu występuje literał pusty łańcuch:Istotna część tego CPython jest
PyImport_ImportModuleLevelObject
funkcją :CPython zgłasza ten wyjątek, jeśli nie mógł znaleźć
package
(nazwa pakietu) winterp->modules
(dostępny jakosys.modules
). Ponieważsys.modules
jest to „słownik, który odwzorowuje nazwy modułów na moduły, które zostały już załadowane” , jest teraz jasne, że moduł nadrzędny musi zostać jawnie zaimportowany absolutnie przed wykonaniem importu względnego .Uwaga: Łatka z wydania 18018 dodała kolejny
if
blok , który zostanie wykonany przed powyższym kodem:Jeśli
package
(tak jak powyżej) jest pusty ciąg, pojawi się komunikat o błędzieJednak zobaczysz to tylko w Pythonie 3.6 lub nowszym.
Rozwiązanie nr 1: Uruchom skrypt za pomocą -m
Rozważmy katalog (który jest pakietem Python ):
Wszystkie pliki w pakiecie zaczynają się od tych samych 2 linii kodu:
Uwzględniam te dwie linie tylko po to, aby porządek operacji był oczywisty. Możemy je całkowicie zignorować, ponieważ nie mają one wpływu na wykonanie.
__init__.py i module.py zawierają tylko te dwie linie (tzn. są faktycznie puste).
standalone.py dodatkowo próbuje zaimportować module.py poprzez import względny:
Wiemy, że
/path/to/python/interpreter package/standalone.py
to się nie powiedzie. Możemy jednak uruchomić moduł z-m
opcją wiersza poleceń , która „wyszukasys.path
nazwany moduł i wykona jego zawartość jako__main__
moduł” :-m
wykonuje wszystkie operacje importowania dla Ciebie i automatycznie ustawia__package__
, ale możesz to zrobić samodzielnie wRozwiązanie nr 2: Ustaw __package__ ręcznie
Potraktuj to jako dowód koncepcji, a nie faktyczne rozwiązanie. Nie nadaje się do użycia w kodzie rzeczywistym.
PEP 366 ma obejście tego problemu, jednak jest niekompletne, ponieważ
__package__
samo ustawienie nie wystarczy. Będziesz musiał zaimportować co najmniej N poprzednich pakietów w hierarchii modułów, gdzie N jest liczbą katalogów nadrzędnych (w stosunku do katalogu skryptu), które będą wyszukiwane w poszukiwaniu importowanego modułu.A zatem,
Dodaj katalog nadrzędny n-tego poprzednika bieżącego modułu do
sys.path
Usuń katalog bieżącego pliku z
sys.path
Zaimportuj moduł nadrzędny bieżącego modułu, używając jego pełnej nazwy
Ustaw
__package__
na pełną nazwę od 2Wykonaj import względny
Pożyczę pliki z rozwiązania nr 1 i dodam kilka dodatkowych paczek:
Tym razem standalone.py zaimportuje module.py z pakietu pakietu przy użyciu następującego importu względnego
Będziemy musieli poprzedzić ten wiersz kodem bojlera, aby działał.
Pozwala nam to wykonać standalone.py według nazwy pliku:
Bardziej ogólne rozwiązanie zawarte w funkcji można znaleźć tutaj . Przykładowe użycie:
Rozwiązanie nr 3: Użyj importu bezwzględnego i setuptools
Kroki to -
Zastąp wyraźny import względny równoważnym importem absolutnym
Zainstaluj,
package
aby można go było importowaćNa przykład struktura katalogów może wyglądać następująco
gdzie jest plik setup.py
Pozostałe pliki zostały zapożyczone z rozwiązania nr 1 .
Instalacja pozwoli ci zaimportować pakiet niezależnie od katalogu roboczego (zakładając, że nie wystąpią problemy z nazewnictwem).
Możemy zmodyfikować standalone.py, aby wykorzystać tę zaletę (krok 1):
Zmień katalog roboczy na
project
i uruchom/path/to/python/interpreter setup.py install --user
(--user
instaluje pakiet w katalogu pakietów witryny ) (krok 2):Sprawdźmy, czy można teraz uruchomić standalone.py jako skrypt:
Uwaga : jeśli zdecydujesz się zejść tą drogą, lepiej byłoby używać środowisk wirtualnych do instalowania pakietów w izolacji.
Rozwiązanie nr 4: Użyj importu bezwzględnego i trochę kodu typu „kocioł”
Szczerze mówiąc, instalacja nie jest konieczna - możesz dodać do skryptu trochę kodu, aby importowanie absolutne działało.
Pożyczę pliki z Rozwiązania nr 1 i zmienię standalone.py :
Dodaj katalog nadrzędny pakiet aby
sys.path
przed przystąpieniem do importu czegokolwiek z pakietu za pomocą importu bezwzględne:Zastąp import względny importem bezwzględnym:
standalone.py działa bez problemów:
Uważam, że powinienem cię ostrzec: staraj się tego nie robić, zwłaszcza jeśli twój projekt ma złożoną strukturę.
Na marginesie, PEP 8 zaleca stosowanie importu bezwzględnego, ale stwierdza, że w niektórych scenariuszach dopuszczalny jest wyraźny import względny:
źródło
__package__
ręcznie, jeśli nazwa ma__main__
rozwiązać problem?imp
modułu i__package__
odpowiednio go ustawić, ale wynik jest wyraźnie anty-wzorem.AttributeError: 'PosixPath' object has no attribute 'path'
.Umieść to w pliku __init__.py pakietu :
Zakładając, że Twój pakiet wygląda następująco:
Teraz używaj regularnego importu w swoim pakiecie, na przykład:
Działa to zarówno w Pythonie 2, jak i 3.
źródło
__init__.py
rozwiąże w zasadzie wszystkie względne błędy importu.sys.path
ponieważ obawiam się, że może to wpłynąć na inny kod. (Częściowo dzieje się tak, ponieważ nie znam zawiłości tego, jak to działa.)Natknąłem się na ten problem. Obejście problemu włamania jest importowane przez blok if / else w następujący sposób:
źródło
except:
jest zły. użyjexcept ImportError:
zamiast tego!SystemError
tutaj (Py 3.4)if __name__ == '__main__': from mymod import as_int; else: from .mymod import as_int
.Mam nadzieję, że będzie to dla kogoś wartościowe - przeszedłem przez pół tuzina postów z przepływem stosów, próbując znaleźć względny import podobny do tego, co zamieszczono powyżej. Ustawiłem wszystko zgodnie z sugestią, ale wciąż uderzałem
ModuleNotFoundError: No module named 'my_module_name'
Ponieważ właśnie rozwijałem się lokalnie i bawiłem, nie utworzyłem / nie uruchomiłem
setup.py
pliku. Najwyraźniej też tego nie ustawiłemPYTHONPATH
.Zdałem sobie sprawę, że kiedy uruchomiłem mój kod, tak jak wtedy, gdy testy były w tym samym katalogu co moduł, nie mogłem znaleźć mojego modułu:
Kiedy jednak wyraźnie podałem ścieżkę, rzeczy zaczęły działać:
Jeśli więc ktoś wypróbuje kilka sugestii, uważa, że jego kod jest poprawnie skonstruowany i nadal znajduje się w podobnej sytuacji, jak ja, spróbuj wykonać jedną z poniższych czynności, jeśli nie wyeksportujesz bieżącego katalogu do PYTHONPATH:
$ PYTHONPATH=. python3 test/my_module/module_test.py
PYTHONPATH=.
, utwórzsetup.py
plik o treści takiej jak poniżej i uruchom,python setup.py development
aby dodać pakiety do ścieżki:źródło
Musiałem uruchomić Python3 z głównego katalogu projektu, aby działał.
Na przykład, jeśli projekt ma następującą strukturę:
Rozwiązanie
Uruchomiłbym python3 w folderze project_demo /, a następnie wykonałem
źródło
Aby uniknąć tego problemu, opracowałem rozwiązanie z pakietem przepakowywania , które działało dla mnie od pewnego czasu. Dodaje górny katalog do ścieżki lib:
Dzięki przepakowaniu można dokonać względnego importu, który działa w szerokim zakresie przypadków, przy użyciu inteligentnej strategii (sprawdzanie stosu wywołań).
źródło
jeśli oba pakiety znajdują się w ścieżce importu (sys.path), a żądany moduł / klasa znajduje się w przykładzie / example.py, to aby uzyskać dostęp do klasy bez importu względnego, spróbuj:
źródło
Myślę, że najlepszym rozwiązaniem jest utworzenie pakietu dla modułu: Oto więcej informacji o tym, jak to zrobić.
Gdy masz już pakiet, nie musisz się martwić o import względny, możesz po prostu wykonać import bezwzględny.
źródło
Miałem podobny problem: potrzebowałem usługi Linux i wtyczki cgi, które używają wspólnych stałych do współpracy. „Naturalnym” sposobem na to jest umieszczenie ich w pliku init .py pakietu, ale nie mogę uruchomić wtyczki cgi z parametrem -m.
Moje ostateczne rozwiązanie było podobne do rozwiązania nr 2 powyżej:
Wadą jest to, że musisz poprzedzić stałe (lub wspólne funkcje) pkg:
źródło