Względny import w Pythonie 3
Chcę zaimportować funkcję z innego pliku w tym samym katalogu.
Czasami mi to działa z from .mymodule import myfunction
ale czasami dostaję:
SystemError: Parent module '' not loaded, cannot perform relative import
Czasami działa z from mymodule import myfunction
, ale czasami dostaję też:
SystemError: Parent module '' not loaded, cannot perform relative import
Nie rozumiem tu logiki i nie mogłem znaleźć żadnego wyjaśnienia. To wygląda zupełnie przypadkowo.
Czy ktoś mógłby mi wyjaśnić, jaka jest logika tego wszystkiego? 8 answers
Niestety ten moduł musi znajdować się wewnątrz pakietu, a także czasami musi być uruchamiany jako skrypt. Każdy pomysł, jak mógłbym osiągnąć?
To dość powszechne mieć taki układ...
main.py
mypackage/
__init__.py
mymodule.py
myothermodule.py
...z mymodule.py
w ten sposób...
#!/usr/bin/env python3
# Exported function
def as_int(a):
return int(a)
# Test function for module
def _test():
assert as_int('1') == 1
if __name__ == '__main__':
_test()
...a myothermodule.py
Jak to...
#!/usr/bin/env python3
from .mymodule import as_int
# Exported function
def add(a, b):
return as_int(a) + as_int(b)
# Test function for module
def _test():
assert add('1', '1') == 2
if __name__ == '__main__':
_test()
...i main.py
w ten sposób...
#!/usr/bin/env python3
from mypackage.myothermodule import add
def main():
print(add('1', '1'))
if __name__ == '__main__':
main()
...który działa dobrze, gdy uruchamiasz main.py
lub mypackage/mymodule.py
, ale nie działa z mypackage/myothermodule.py
, ze względu na względną import...
from .mymodule import as_int
Sposób, w jaki powinieneś to prowadzić jest...
python3 -m mypackage.myothermodule
...ale jest nieco gadatliwy i nie miesza się dobrze z linią shebang jak #!/usr/bin/env python3
.
Najprostszą poprawką w tym przypadku, zakładając, że nazwa {[16] } jest globalnie unikalna, byłoby unikanie importu względnego i po prostu używanie...
from mymodule import as_int
...chociaż, jeśli nie jest unikalny lub struktura pakietów jest bardziej złożona, musisz dołączyć katalog zawierający Twój katalog PYTHONPATH
, i zrób to tak...
from mypackage.mymodule import as_int
...lub jeśli chcesz, aby działało "po wyjęciu z pudełka", możesz najpierw przerobić PYTHONPATH
w kodzie za pomocą tego...
import sys
import os
PACKAGE_PARENT = '..'
SCRIPT_DIR = os.path.dirname(os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__))))
sys.path.append(os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT)))
from mypackage.mymodule import as_int
To trochę bolesne, ale jest wskazówka, dlaczego w e-mailu napisanym przez pewnego Guido van Rossuma...
Jestem -1 na tym i na innych proponowanych twiddlingach
__main__
maszyny. Jedynym przypadkiem użycia wydaje się uruchamianie skryptów, które zdarzają się żyć w katalogu modułu, który mam zawsze postrzegane jako antypattern. Żeby zmienić zdanie musisz mnie przekonać, że nie jest.
Czy uruchamianie skryptów wewnątrz pakietu jest antypaternem, czy nie jest subiektywne, ale osobiście uważam, że jest to naprawdę przydatne w pakiecie, który mam, który zawiera niestandardowe widżety wxPython, więc mogę uruchomić skrypt dla dowolnego pliku źródłowego, aby wyświetlić wx.Frame
zawierający tylko ten widżet do celów testowych.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-07 13:46:06
Wyjaśnienie
From PEP 328
Import względny używa atrybutu __Nazwa__ modułu, aby określić, że pozycji modułu w hierarchii pakietów. Jeśli nazwa modułu nie zawiera żadnych informacji o pakiecie (np. jest ustawiony na ' _ _ main__') wtedy względne importowanie jest rozwiązywane tak, jakby moduł był najwyższym poziomem moduł , niezależnie od tego, gdzie moduł znajduje się w pliku system.
W pewnym momencie PEP 338 328:
... względny import opiera się na _ _ nazwa _ _ , aby określić aktualny pozycji modułu w hierarchii pakietów. W module głównym wartość __name _ _ jest zawsze ' _ _ main _ _ ' , więc jawny względny import zawsze nie będzie działać (ponieważ działają tylko dla modułu wewnątrz pakietu)
I aby rozwiązać problem, PEP 366 wprowadził zmienną najwyższego poziomu __package__
:
Dodając nowy atrybut poziomu modułu, ten PEP pozwala na względne importuje do pracy automatycznie, jeśli moduł jest wykonywany przy użyciu - M Zamiana. Niewielka ilość kotła w samym module pozwoli względny import działa, gdy plik jest wykonywany według nazwy. [...] Gdy [atrybut] jest obecny, względny import będzie oparty na tym atrybutie zamiast atrybutu module _ _ name _ _ . [...] Gdy głównym moduł jest określony przez jego nazwę pliku, wtedy atrybut __pakiet__ zostanie ustawiony na None. [...] gdy system importu napotka jawny względny import w moduł bez _ _ package _ _ set (lub z nim ustawionym na None), będzie Oblicz i zapisz prawidłową wartość (__ / align = "left" / rpartition (".')[0] dla zwykłych modułów i _ _ nazwa _ _ dla modułów inicjalizacji pakietów)
(moje)
Jeśli __name__
jest '__main__'
, __name__.rpartition('.')[0]
zwraca pusty łańcuch. Dlatego w opisie błędu znajduje się pusty ciąg znaków:
SystemError: Parent module '' not loaded, cannot perform relative import
Odpowiednia część CPython ' S PyImport_ImportModuleLevelObject
funkcja:
if (PyDict_GetItem(interp->modules, package) == NULL) {
PyErr_Format(PyExc_SystemError,
"Parent module %R not loaded, cannot perform relative "
"import", package);
goto error;
}
CPython podnosi ten wyjątek, jeśli nie mógł znaleźć package
(nazwy pakietu) w interp->modules
(dostępnej jako sys.modules
). Ponieważ sys.modules
jest "słownikiem, który mapuje nazwy modułów do modułów, które zostały już załadowane" , jest teraz jasne, że moduł nadrzędny musi być jawnie importowany bezwzględnie przed wykonaniem importu względnego .
Uwaga: patch z numer 18018 dodał kolejny if
Blok , który zostanie wykonany przed powyższy kod:
if (PyUnicode_CompareWithASCIIString(package, "") == 0) {
PyErr_SetString(PyExc_ImportError,
"attempted relative import with no known parent package");
goto error;
} /* else if (PyDict_GetItem(interp->modules, package) == NULL) {
...
*/
Jeśli package
(tak samo jak powyżej) jest pustym łańcuchem, komunikat o błędzie będzie wynosił
ImportError: attempted relative import with no known parent package
Jednak zobaczysz to tylko w Pythonie 3.6 lub nowszym.
Rozwiązanie # 1: Uruchom skrypt za pomocą -m
Rozważmy katalog (który jest pakietem Pythona ):
.
├── package
│ ├── __init__.py
│ ├── module.py
│ └── standalone.py
Wszystkie pliki w pakiecie zaczynają się tymi samymi 2 linijkami kodu:
from pathlib import Path
print('Running' if __name__ == '__main__' else 'Importing', Path(__file__).resolve())
Włączam te dwie linie tylko aby kolejność operacji była oczywista. Możemy je całkowicie zignorować, ponieważ nie wpływają na egzekucję.
__init__.py i module.py zawierają tylko te dwie linie (tzn. są one skutecznie pusta).
standalone.py dodatkowo próbuje zaimportować module.py Poprzez względny import:
from . import module # explicit relative import
Jesteśmy świadomi, że zawiedzie. Możemy jednak uruchomić moduł z -m
command line option that will " search sys.path
dla nazwanego modułu i wykonaj jego zawartość jako moduł __main__
":
vaultah@base:~$ python3 -i -m package.standalone
Importing /home/vaultah/package/__init__.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/module.py
>>> __file__
'/home/vaultah/package/standalone.py'
>>> __package__
'package'
>>> # The __package__ has been correctly set and module.py has been imported.
... # What's inside sys.modules?
... import sys
>>> sys.modules['__main__']
<module 'package.standalone' from '/home/vaultah/package/standalone.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>
-m
robi wszystko za Ciebie i automatycznie ustawia __package__
, ale możesz to zrobić siebie w
Rozwiązanie # 2: Ustaw__ pakiet _ _ ręcznie
proszę traktować to jako dowód koncepcji, a nie rzeczywiste rozwiązanie. Nie nadaje się do użycia w kodzie rzeczywistym.
PEP 366 ma obejście tego problemu, jednak jest niekompletne, ponieważ samo ustawienie __package__
nie wystarczy. Będziesz musiał zaimportować co najmniej N poprzedzające pakiety w hierarchii modułów, gdzie N jest liczbą katalogi nadrzędne (względem katalogu skryptu), które będą wyszukiwane dla importowanego modułu.
Tak więc,
Dodaj Katalog nadrzędny NTH poprzednika bieżącego modułu do
sys.path
Usuń katalog bieżącego pliku z
sys.path
-
Import modułu nadrzędnego bieżącego modułu przy użyciu jego w pełni kwalifikowanej nazwy
Ustaw
__package__
na pełną nazwę od 2-
Wykonaj względny import
Pożyczę pliki z rozwiązania # 1 i dodam jeszcze kilka podpakietów:
package
├── __init__.py
├── module.py
└── subpackage
├── __init__.py
└── subsubpackage
├── __init__.py
└── standalone.py
Tym razem standalone.py zaimportuje module.py Z pakietu za pomocą następującego względnego importu
from ... import module # N = 3
Musimy poprzedzić tę linię kodem kotła, aby to zadziałało.
import sys
from pathlib import Path
if __name__ == '__main__' and __package__ is None:
file = Path(__file__).resolve()
parent, top = file.parent, file.parents[3]
sys.path.append(str(top))
try:
sys.path.remove(str(parent))
except ValueError: # Already removed
pass
import package.subpackage.subsubpackage
__package__ = 'package.subpackage.subsubpackage'
from ... import module # N = 3
Pozwala nam na wykonanie standalone.py wg nazwy pliku:
vaultah@base:~$ python3 package/subpackage/subsubpackage/standalone.py
Running /home/vaultah/package/subpackage/subsubpackage/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/subpackage/__init__.py
Importing /home/vaultah/package/subpackage/subsubpackage/__init__.py
Importing /home/vaultah/package/module.py
Bardziej ogólne rozwiązanie zawinięte w funkcję można znaleźć tutaj . Przykład użycia:
if __name__ == '__main__' and __package__ is None:
import_parents(level=3) # N = 3
from ... import module
from ...module.submodule import thing
Rozwiązanie # 3: Użyj importu bezwzględnego i setuptools
Kroki są-
-
Zastąp jawny import względny równoważnym importem bezwzględnym
-
Zainstaluj
package
, aby można było go zaimportować
Na przykład struktura katalogów może być follows
.
├── project
│ ├── package
│ │ ├── __init__.py
│ │ ├── module.py
│ │ └── standalone.py
│ └── setup.py
Gdzie setup.py jest
from setuptools import setup, find_packages
setup(
name = 'your_package_name',
packages = find_packages(),
)
Reszta plików została zapożyczona z rozwiązania # 1 .
Instalacja pozwoli Ci zaimportować pakiet niezależnie od katalogu roboczego (zakładając, że nie będzie problemów z nazwami).
Możemy zmodyfikować standalone.py aby skorzystać z tej przewagi (Krok 1):
from package import module # absolute import
Zmień katalog roboczy na project
i uruchom /path/to/python/interpreter setup.py install --user
(--user
instaluje pakiet w Twoim site-packages directory ) (Krok 2):
vaultah@base:~$ cd project
vaultah@base:~/project$ python3 setup.py install --user
Sprawdźmy, czy teraz Można uruchomić standalone.py jako scenariusz:
vaultah@base:~/project$ python3 -i package/standalone.py
Running /home/vaultah/project/package/standalone.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>
Uwaga: jeśli zdecydujesz się pójść tą drogą, lepiej będzie używać wirtualnych środowisk do instalowania pakietów w izolacji.
Rozwiązanie # 4: użyj importu bezwzględnego i kodu kotła
Szczerze mówiąc instalacja nie jest konieczna - możesz dodać trochę kodu do Twój skrypt, aby import absolutny działał.
Pożyczę pliki z rozwiązanie #1 i zmienię standalone.py :
-
Dodaj Katalog nadrzędny pakietu do
sys.path
przed próbą zaimportowania czegokolwiek z pakietu przy użyciu importu bezwzględnego:import sys from pathlib import Path # if you haven't already done so file = Path(__file__).resolve() parent, root = file.parent, file.parents[1] sys.path.append(str(root)) # Additionally remove the current file's directory from sys.path try: sys.path.remove(str(parent)) except ValueError: # Already removed pass
-
Zastąp względny import przez bezwzględny import:
from package import module # absolute import
standalone.py działa bez problemy:
vaultah@base:~$ python3 -i package/standalone.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>
Czuję, że powinienem cię ostrzec: staraj się tego nie robić, szczególnie 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 możliwy jest bezpośredni import względny:
Import bezwzględny jest zalecany, ponieważ zwykle jest bardziej czytelny i wydają się być lepiej zachowywane (lub przynajmniej dają lepszy błąd wiadomości). [...] Jednak, jawny względny Import jest akceptowalny alternatywa dla importu bezwzględnego, szczególnie w przypadku złożonych układy pakietów, w których użycie importu bezwzględnego byłoby niepotrzebnie gadatliwy.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-31 11:40:35
Natknąłem się na ten problem. Obejście włamania jest importowane za pomocą bloku if / else w następujący sposób:
#!/usr/bin/env python3
#myothermodule
if __name__ == '__main__':
from mymodule import as_int
else:
from .mymodule import as_int
# Exported function
def add(a, b):
return as_int(a) + as_int(b)
# Test function for module
def _test():
assert add('1', '1') == 2
if __name__ == '__main__':
_test()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-13 19:22:45
Włóż to do paczki. init__.py plik :
# For relative imports to work in Python 3.6
import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))
Zakładając, że Twój Pakiet jest taki:
├── project
│ ├── package
│ │ ├── __init__.py
│ │ ├── module1.py
│ │ └── module2.py
│ └── setup.py
Teraz używaj zwykłego importu w pakiecie, jak:
# in module2.py
from module1 import class1
To działa zarówno w Pythonie 2 jak i 3.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-16 12:38:26
Jeśli oba pakiety znajdują się w ścieżce importu (sys.ścieżka), a żądany moduł/Klasa znajduje się w example/example.py, następnie, aby uzyskać dostęp do klasy bez względnego importu, spróbuj:
from example.example import fkt
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-09 22:18:30
Aby rozwiązać ten problem, opracowałem rozwiązanie z repackage pakiet, który działa dla mnie od jakiegoś czasu. Dodaje górny katalog do ścieżki lib:
import repackage
repackage.up()
from mypackage.mymodule import myfunction
Przepakowanie może dokonać względnego importu, który działa w szerokim zakresie przypadków, przy użyciu inteligentnej strategii (sprawdzanie stosu wywołań).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-06 09:42:19
Mam nadzieję, że będzie to miało wartość dla kogoś tam - przejrzałem pół tuzina postów stackoverflow, próbując dowiedzieć się o relatywnych importach podobnych do tych zamieszczonych powyżej. Ustawiłem wszystko zgodnie z sugestią, ale wciąż uderzałem ModuleNotFoundError: No module named 'my_module_name'
Ponieważ tylko rozwijałem lokalnie i bawiłem się, nie stworzyłem/nie uruchomiłem pliku setup.py
. Ja też najwyraźniej nie ustawiłem swojego PYTHONPATH
.
Zdałem sobie sprawę, że kiedy uruchomiłem mój kod, tak jak byłem, gdy testy były w tym samym katalogu jako moduł nie mogłem znaleźć mojego modułu:
$ python3 test/my_module/module_test.py 2.4.0
Traceback (most recent call last):
File "test/my_module/module_test.py", line 6, in <module>
from my_module.module import *
ModuleNotFoundError: No module named 'my_module'
Jednak, kiedy wyraźnie określiłem ścieżkę rzeczy zaczęły działać:
$ PYTHONPATH=. python3 test/my_module/module_test.py 2.4.0
...........
----------------------------------------------------------------------
Ran 11 tests in 0.001s
OK
Tak więc, jeśli ktoś wypróbował kilka sugestii, uważa, że ich kod jest poprawnie skonstruowany i nadal znajduje się w podobnej sytuacji, jak ja, spróbuj jednej z poniższych opcji, jeśli nie wyeksportujesz bieżącego katalogu do swojej ścieżki PYTHONPATH:
- Uruchom kod i jawnie Dołącz ścieżkę w ten sposób:
$ PYTHONPATH=. python3 test/my_module/module_test.py
- aby uniknąć wywołania
PYTHONPATH=.
, Utwórz pliksetup.py
o następującej treści i uruchompython setup.py development
, aby dodać pakiety do ścieżki:
# setup.py from setuptools import setup, find_packages setup( name='sample', packages=find_packages() )
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-17 19:40:32
Dla mnie to musiałem uruchomić python3 z głównego katalogu, aby to działało. Na przykład, jeśli projekt ma następującą strukturę:
Project_demo
/ | main.py
| - - some_package
| - - - - __ init __. py
| - - - - project_configs.py
| - - test
| - - - - test_project_configs.py
Rozwiązanie: uruchomiłbym python3 wewnątrz project_demo i następnie z some_package import project_configs .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-11 18:49:29