Względny import po raz miliardowy
Byłem tu:
- http://www.python.org/dev/peps/pep-0328/
- http://docs.python.org/2/tutorial/modules.html#packages
- Pakiety Pythona: względny IMPORT
- python względny import przykładowy kod nie działa
- Ultimate answer to relative python imports
- Import względny w Pythonie
- Python: wyłączenie importu względnego
I mnóstwo adresów URL że nie skopiowałem, niektóre na tak, niektóre na innych stronach, kiedy myślałem, że szybko będę miał rozwiązanie.
Na zawsze powtarzające się pytanie brzmi: w Windows 7, 32-bitowym Pythonie 2.7.3, jak rozwiązać ten Komunikat" próba względnego importu w pakiecie"? Zbudowałem dokładną replikę paczki na pep-0328:
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleY.py
subpackage2/
__init__.py
moduleZ.py
moduleA.py
Zrobiłem funkcje o nazwie spam i jajka w odpowiednich modułach. Oczywiście, nie zadziałało. Odpowiedź jest najwyraźniej w 4. URL, który wymieniłem, ale to dla mnie wszyscy absolwenci. Była taka odpowiedź na jednym z adresów URL, które odwiedziłem:
Import względny używa atrybutu Nazwa modułu do określenia pozycji tego modułu w hierarchii pakietów. Jeśli nazwa modułu nie zawiera żadnych informacji o pakiecie (np. jest ustawiona na 'main'), to względne importowanie jest rozwiązywane tak, jakby moduł był modułem najwyższego poziomu, niezależnie od tego, gdzie moduł znajduje się w systemie plików.
Powyższa odpowiedź wygląda obiecująco, ale dla mnie to wszystko hieroglify. Więc moje pytanie, Jak sprawić, by Python nie wrócił do mnie "Attempted relative import in non-package"? ma odpowiedź, która zakłada-m, podobno.
Czy ktoś może mi powiedzieć, dlaczego Python daje ten Komunikat o błędzie, co to znaczy przez non-package!, dlaczego i jak definiujesz "pakiet" i precyzyjną odpowiedź w kategoriach łatwych do zrozumienia przez przedszkolaka .
Edit: import został wykonany z konsoli.
8 answers
Skrypt a moduł
Oto Wyjaśnienie. Skrócona wersja jest taka, że istnieje duża różnica między bezpośrednim uruchomieniem pliku Pythona, a importowaniem tego pliku z innego miejsca. sama wiedza, w którym katalogu znajduje się plik, nie decyduje o tym, w jakim pakiecie Python myśli, że jest. to dodatkowo zależy od tego, jak załadujesz plik do Pythona(uruchamiając lub importując).
Istnieją dwa sposoby ładowania pliku Pythona: 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 on ładowany jako moduł, jeśli wykonasz python -m myfile
, lub jeśli zostanie załadowany, gdy w innym pliku zostanie napotkana Instrukcja import
. Może być tylko jeden skrypt najwyższego poziomu na raz; skrypt najwyższego poziomu jest plikiem Pythona, który uruchomiłeś, aby rozpocząć działanie.
Nazewnictwo
Gdy plik jest ładowany, otrzymuje nazwę (która jest przechowywana w jego __name__
atrybut). Jeśli został załadowany jako skrypt najwyższego poziomu, jego nazwa to __main__
. Jeśli został załadowany jako moduł, jego nazwa jest nazwą pliku, poprzedzoną nazwami wszystkich pakietów / podpakietów, których jest częścią, oddzielonych kropkami.
Więc na przykład w twoim przykładzie:
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleA.py
Jeśli zaimportowałeś moduleX
(uwaga: zaimportowałeś, a nie wykonałeś bezpośrednio), jego nazwa będzie package.subpackage1.moduleX
. Jeśli zaimportujesz moduleA
, jego nazwa będzie package.moduleA
. Jednak, jeśli bezpośrednio uruchomić moduleX
z linia poleceń, jej nazwa będzie zamiast tego __main__
, a jeśli bezpośrednio uruchomisz {[8] } z linii poleceń, jej nazwa będzie __main__
. Gdy moduł jest uruchamiany jako skrypt najwyższego poziomu, traci swoją normalną nazwę, a jego nazwa to __main__
.
Dostęp do modułu nie poprzez jego pakiet
Istnieje dodatkowa zmarszczka: Nazwa modułu zależy od tego, czy został zaimportowany "bezpośrednio" z katalogu, w którym się znajduje, czy zaimportowany przez pakiet. To tylko sprawia, że różnica jeśli uruchamiasz Pythona w katalogu i próbujesz zaimportować plik w tym samym katalogu (lub jego podkatalogu). Na przykład, jeśli uruchomisz interpreter Pythona w katalogu package/subpackage1
, a następnie wykonasz import moduleX
, Nazwa moduleX
będzie po prostu moduleX
, a nie package.subpackage1.moduleX
. Dzieje się tak dlatego, że Python dodaje bieżący katalog do swojej ścieżki wyszukiwania podczas uruchamiania; jeśli znajdzie importowany moduł w bieżącym katalogu, nie będzie wiedział, że ten katalog jest częścią pakietu, a pakiet informacje nie staną się częścią nazwy modułu.
Szczególnym przypadkiem jest uruchomienie interpretera interaktywnie(np. po prostu wpisz python
i zacznij wprowadzać kod Pythona w locie). W tym przypadku nazwa tej interaktywnej sesji to __main__
.
Oto kluczowa rzecz dla Twojego Komunikatu o błędzie: jeśli nazwa modułu nie ma kropek, nie jest on uważany za część pakietu . Nie ma znaczenia, gdzie plik rzeczywiście znajduje się na dysku. Liczy się tylko to, co jego nazwa jest, a jej nazwa zależy od tego, jak go załadowałeś.
Teraz spójrz na cytat, który zawarłeś w swoim pytaniu:
Import względny używa atrybutu Nazwa modułu do określenia pozycji tego modułu w hierarchii pakietów. Jeśli nazwa modułu nie zawiera żadnych informacji o pakiecie (np. jest ustawiona na 'main'), względne importowanie jest rozwiązywane tak, jakby moduł był modułem najwyższego poziomu, niezależnie od tego, gdzie moduł znajduje się w pliku system.
Względny import...
Import względny używa nazwy modułu , aby określić, gdzie znajduje się on w pakiecie. Gdy używasz względnego importu, takiego jak from .. import foo
, kropki wskazują, aby zwiększyć pewną liczbę poziomów w hierarchii pakietów. Na przykład, jeśli obecna nazwa modułu to package.subpackage1.moduleX
, to ..moduleA
oznacza package.moduleA
. Aby from .. import
zadziałało, Nazwa modułu musi mieć co najmniej tyle kropek, ile jest w import
oświadczenie.
... są względne tylko w pakiecie
Jednakże, jeśli nazwa Twojego modułu to __main__
, nie jest on uważany za znajdujący się w pakiecie. Jego nazwa nie ma kropek, dlatego nie można używać from .. import
wypowiedzi wewnątrz niego. Jeśli spróbujesz to zrobić, pojawi się błąd "relative-import in non-package".
Skrypty nie mogą importować względnego
Prawdopodobnie próbowałeś uruchomić moduleX
lub tym podobne z linii poleceń. Kiedy to zrobiłeś, jego nazwa została ustawiona na __main__
, co oznacza, że względny import w nim nie powiedzie się, ponieważ jego nazwa nie pokazuje, że znajduje się w pakiecie. Zauważ, że stanie się tak również, jeśli uruchomisz Pythona 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 "za wcześnie", nie zdając sobie sprawy, że jest częścią pakietu.
Pamiętaj również, że gdy uruchomisz interaktywny interpreter, "nazwa" tego sesja interaktywna jest zawsze __main__
. Tak więc nie można importować względnych danych bezpośrednio z interaktywnej sesji. Import względny jest przeznaczony tylko do użytku w plikach modułów.
Dwa rozwiązania:
Jeśli naprawdę chcesz uruchomić
moduleX
bezpośrednio, ale nadal chcesz, aby był on uważany za część pakietu, możesz to zrobićpython -m package.subpackage1.moduleX
.-m
mówi Pythonowi, aby załadował go jako moduł, a nie jako skrypt najwyższego poziomu.A może nie właściwie chcę uruchomić
moduleX
, po prostu chcesz uruchomić jakiś inny skrypt, powiedzmymyfile.py
, który używa funkcji wewnątrzmoduleX
. Jeśli tak jest, postawmyfile.py
somewhere else --- Nie wewnątrz katalogupackage
-- i uruchom go. Jeśli wewnątrzmyfile.py
będziesz robił takie rzeczy jakfrom package.moduleA import spam
, to będzie dobrze działać.
Uwagi
Dla każdego z tych rozwiązań, katalog pakietów (
package
w twoim przykładzie) musi być dostępny ze ścieżki wyszukiwania modułu Pythona (sys.path
). Jeśli tak nie jest, nie będziesz w stanie w ogóle niezawodnie korzystać z niczego w pakiecie.Od wersji Pythona 2.6 "Nazwa" modułu dla celów rozdzielczości pakietów jest określana nie tylko przez jego atrybuty
__name__
, ale także przez atrybut__package__
. Dlatego unikam używania jawnego symbolu__name__
w odniesieniu do "nazwy"modułu. Od Pythona 2.6 "Nazwa" modułu jest efektywnie__package__ + '.' + __name__
, lub po prostu__name__
Jeśli__package__
jestNone
.)
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-31 17:04:47
To jest naprawdę problem w Pythonie. źródłem zamieszania jest to, że ludzie błędnie biorą względny import jako względną ścieżkę, która nie jest.
Na przykład gdy piszesz w faa.py :
from .. import foo
To ma znaczenie tylko wtedy, gdy .y został zidentyfikowany i załadowany przez Pythona, podczas wykonywania, jako część pakietu. W takim przypadku nazwa modułu dla faa.py będzie na przykład some_packagename.faa . Jeśli plik został załadowany tylko dlatego, że znajduje się w bieżącym katalogu, gdy python jest uruchomiony, jego nazwa nie odnosi się do żadnego pakietu i ostatecznie względny import nie powiedzie się.
Prostym rozwiązaniem, aby odwoływać się do modułów w bieżącym katalogu, jest użycie tego:
if __package__ is None or __package__ == '':
#uses current directory visibility
import foo
else:
#uses current package visibility
from . import foo
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-03-25 19:50:43
Oto ogólny przepis, zmodyfikowany tak, aby pasował jako przykład, który używam teraz do radzenia sobie z bibliotekami Pythona napisanymi jako pakiety, które zawierają współzależne pliki, gdzie chcę móc testować ich części fragmentarycznie. Nazwijmy to lib.foo
i powiedzmy, że potrzebuje dostępu do lib.fileA
dla funkcji f1
i f2
oraz lib.fileB
dla klasy Class3
.
Zawarłem kilka print
wezwań, aby pomóc zilustrować, jak to działa. W praktyce chciałbyś je usunąć (a może również linia from __future__ import print_function
).
Ten konkretny przykład jest zbyt prosty, aby pokazać, kiedy naprawdę musimy wstawić wpis do sys.path
. (Zobacz odpowiedź Larsa Dla Przypadku, w którym potrzebujemy, gdy mamy dwa lub więcej poziomów katalogów pakietów, a następnie używamy os.path.dirname(os.path.dirname(__file__))
-ale tak naprawdę nie boli tutaj też.) Jest również wystarczająco bezpieczny, aby zrobić to bez testu if _i in sys.path
. Jeśli jednak każdy importowany plik wstawia tę samą ścieżkę-na przykład, jeśli zarówno fileA
, jak i fileB
chcą aby zaimportować narzędzia z pakietu - to zaśmieca sys.path
tą samą ścieżką wiele razy, więc miło jest mieć if _i not in sys.path
w kotle.
from __future__ import print_function # only when showing how this works
if __package__:
print('Package named {!r}; __name__ is {!r}'.format(__package__, __name__))
from .fileA import f1, f2
from .fileB import Class3
else:
print('Not a package; __name__ is {!r}'.format(__name__))
# these next steps should be used only with care and if needed
# (remove the sys.path manipulation for simple cases!)
import os, sys
_i = os.path.dirname(os.path.abspath(__file__))
if _i not in sys.path:
print('inserting {!r} into sys.path'.format(_i))
sys.path.insert(0, _i)
else:
print('{!r} is already in sys.path'.format(_i))
del _i # clean up global name space
from fileA import f1, f2
from fileB import Class3
... all the code as usual ...
if __name__ == '__main__':
import doctest, sys
ret = doctest.testmod()
sys.exit(0 if ret.failed == 0 else 1)
Idea jest taka (i zauważ, że wszystkie te funkcje są takie same w python2. 7 i python 3.x):
Jeśli jest uruchamiany jako
import lib
lubfrom lib import foo
jako zwykły import pakietów z zwykłego kodu,__package
jestlib
, A__name__
jestlib.foo
. Wybieramy pierwszą ścieżkę kodu, importujemy z.fileA
, itd.-
If run as
python lib/foo.py
,__package__
będzie brak i {[26] } będzie__main__
.Wybieramy drugą ścieżkę kodu. Katalog
lib
będzie już wsys.path
, więc nie trzeba go dodawać. Importujemy zfileA
, itp. Jeśli jest uruchamiany w katalogu
lib
jakopython foo.py
, zachowanie jest takie samo jak w przypadku przypadku 2.-
Jeśli jest uruchomiony w katalogu
lib
jakopython -m foo
, zachowanie jest podobne do przypadków 2 i 3. Jednak ścieżka do katalogulib
nie znajduje się wsys.path
, więc dodaj go przed zaimportowaniem. To samo dotyczy Pythona, a następnieimport foo
.(od
.
jest wsys.path
, tak naprawdę nie musimy dodawać absolutnej wersji ścieżki tutaj. To jest miejsce, w którym głębsza struktura zagnieżdżania pakietów, w którym chcemy to zrobićfrom ..otherlib.fileC import ...
, robi różnicę. Jeśli tego nie robisz, możesz całkowicie pominąć całą manipulacjęsys.path
.)
Uwagi
Jest jeszcze dziwactwo. Jeśli zaczniesz to wszystko od Na Zewnątrz:
$ python2 lib.foo
Lub:
$ python3 lib.foo
Zachowanie zależy od zawartości lib/__init__.py
. Jeśli istnieje i jest puste, wszystko jest dobrze:
Package named 'lib'; __name__ is '__main__'
Ale jeśli lib/__init__.py
sam importuje routine
, aby mógł eksportować routine.name
bezpośrednio jako lib.name
, otrzymujesz:
$ python2 lib.foo
Package named 'lib'; __name__ is 'lib.foo'
Package named 'lib'; __name__ is '__main__'
Oznacza to, że moduł zostanie zaimportowany dwa razy, raz za pośrednictwem pakietu, a następnie ponownie jako __main__
, aby uruchomić kod main
. Python 3.6 i Później ostrzegają o tym:
$ python3 lib.routine
Package named 'lib'; __name__ is 'lib.foo'
[...]/runpy.py:125: RuntimeWarning: 'lib.foo' found in sys.modules
after import of package 'lib', but prior to execution of 'lib.foo';
this may result in unpredictable behaviour
warn(RuntimeWarning(msg))
Package named 'lib'; __name__ is '__main__'
The Ostrzeżenie jest nowe, ale ostrzeżenie o zachowaniu nie jest. Jest to część tego, co niektórzy nazywają pułapką podwójnego importu. (Aby uzyskać dodatkowe informacje patrz wydanie 27487. Nick Coghlan says:
Ta Następna pułapka istnieje we wszystkich bieżących wersjach Pythona, w tym 3.3, i może być podsumowana w następujących ogólnych wytycznych: "nigdy nie dodawaj katalogu pakietu, ani żadnego katalogu wewnątrz pakietu, bezpośrednio do ścieżki Pythona".
Zauważ, że podczas gdy my naruszamy tę regułę, robimy to tylko, gdy ładowany plik jest , a nie jest ładowany jako część pakietu, a nasza modyfikacja jest specjalnie zaprojektowana, aby umożliwić nam dostęp do innych plików w tym pakiecie. (I, jak zauważyłam, prawdopodobnie nie powinniśmy tego robić w ogóle dla pakietów jednopoziomowych. Jeśli chcemy być extra-clean, możemy napisać to od nowa, np.:]}
import os, sys
_i = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _i not in sys.path:
sys.path.insert(0, _i)
else:
_i = None
from sub.fileA import f1, f2
from sub.fileB import Class3
if _i:
sys.path.remove(_i)
del _i
Oznacza to, że modyfikujemy sys.path
wystarczająco długo, aby osiągnąć nasz import, a następnie odkładamy go z powrotem tak, jak był (usunięcie jednej kopii _i
wtedy i tylko wtedy, gdy dodaliśmy jedną kopię _i
).
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-05-23 11:54:50
Oto jedno rozwiązanie, którego nie polecam, ale może być przydatne w niektórych sytuacjach, w których moduły po prostu nie zostały wygenerowane:
import os
import sys
parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(parent_dir_name + "/your_dir")
import your_script
your_script.a_function()
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
2016-06-21 02:06:19
__name__
zmienia się w zależności od tego, czy dany kod jest uruchamiany w globalnej przestrzeni nazw, czy jako część zaimportowanego modułu.
Jeśli kod nie jest uruchomiony w przestrzeni globalnej, __name__
będzie nazwą modułu. Jeśli moduł działa w globalnej przestrzeni nazw-na przykład, jeśli wpiszesz go do konsoli lub uruchomisz moduł jako skrypt używając python.exe yourscriptnamehere.py
, to __name__
staje się "__main__"
.
Zobaczysz wiele kodu Pythona z if __name__ == '__main__'
jest używany do testowania, czy kod jest uruchamiany z globalnego przestrzeń nazw-która pozwala na posiadanie modułu, który służy jako skrypt.
Próbowałeś zrobić import z konsoli?
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-04-21 17:02:41
Miałem podobny problem, w którym nie chciałem zmieniać wyszukiwania modułu Pythona path i potrzebne do załadowania modułu relatywnie ze skryptu (pomimo "skrypty nie mogą importować relatywnie z wszystkimi" , jak brenbarn wyjaśnił ładnie powyżej).
Więc użyłem następujący hack. Niestety, opiera się na module imp
, który
stała się przestarzała od wersji 3.4, która została wycofana na rzecz importlib
.
(Czy jest to możliwe również z importlib
? Nie wiem.) Mimo to hack działa na teraz.
Przykład dostępu do członków moduleX
w subpackage1
ze skryptu znajdującego się w folderze subpackage2
:
#!/usr/bin/env python3
import inspect
import imp
import os
def get_script_dir(follow_symlinks=True):
"""
Return directory of code defining this very function.
Should work from a module as well as from a script.
"""
script_path = inspect.getabsfile(get_script_dir)
if follow_symlinks:
script_path = os.path.realpath(script_path)
return os.path.dirname(script_path)
# loading the module (hack, relying on deprecated imp-module)
PARENT_PATH = os.path.dirname(get_script_dir())
(x_file, x_path, x_desc) = imp.find_module('moduleX', [PARENT_PATH+'/'+'subpackage1'])
module_x = imp.load_module('subpackage1.moduleX', x_file, x_path, x_desc)
# importing a function and a value
function = module_x.my_function
VALUE = module_x.MY_CONST
Czystszym podejściem wydaje się być modyfikacja sys.ścieżka używana do ładowania modułów, o której wspomniał Federico.
#!/usr/bin/env python3
if __name__ == '__main__' and __package__ is None:
from os import sys, path
# __file__ should be defined in this case
PARENT_DIR = path.dirname(path.dirname(path.abspath(__file__)))
sys.path.append(PARENT_DIR)
from subpackage1.moduleX import *
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
2016-07-19 10:28:28
Więc po omówieniu tego wraz z wieloma innymi, natknąłem się na notatkę opublikowaną przez Doriana Bw tym artykule, która rozwiązała konkretny problem, który miałem, gdzie będę rozwijać moduły I klasy do użytku z usługą internetową, ale chcę również być w stanie przetestować je podczas kodowania, korzystając z udogodnień debuggera w PyCharm. Aby uruchomić testy w samodzielnej klasie, dodałbym na końcu pliku mojej klasy:
if __name__ == '__main__':
# run test code here...
Ale gdybym chciał importować inne klasy lub moduły w tym samym folderze, musiałbym wtedy zmienić wszystkie moje polecenia import z notacji względnej NA odniesienia lokalne (tj. usunąć kropkę (.)) Ale po przeczytaniu sugestii Doriana, wypróbowałem jego "one-liner" i zadziałało! Mogę teraz testować w PyCharm i pozostawić mój kod testowy na miejscu, gdy używam klasy w innej klasie testowanej, lub gdy używam go w moim serwisie internetowym!
# import any site-lib modules first, then...
import sys
parent_module = sys.modules['.'.join(__name__.split('.')[:-1]) or '__main__']
if __name__ == '__main__' or parent_module.__name__ == '__main__':
from codex import Codex # these are in same folder as module under test!
from dblogger import DbLogger
else:
from .codex import Codex
from .dblogger import DbLogger
Instrukcja if sprawdza, czy uruchamiamy ten moduł jako main czy jest używany w innym module, który jest testowany jako main . Być może jest to oczywiste, ale oferuję tę notatkę tutaj na wypadek, gdyby ktoś inny sfrustrowany względnymi problemami z importem powyżej mógł z niej skorzystać.
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-10-05 01:08:26
Import względny używa atrybutu Nazwa modułu do określenia pozycji tego modułu w hierarchii pakietów. Jeśli nazwa modułu nie zawiera żadnych informacji o pakiecie (np. jest ustawiona na 'main'), to względne importowanie jest rozwiązywane tak, jakby moduł był modułem najwyższego poziomu, niezależnie od tego, gdzie moduł znajduje się w systemie plików.
Napisał mały pakiet Pythona do PyPi, który może pomóc widzom tego pytania. Pakiet działa jako obejście, jeśli jeden chce móc uruchamiać pliki Pythona zawierające importujące pakiety z górnego poziomu z pakietu / projektu bez bezpośredniego znajdowania się w katalogu importującego plik. https://pypi.org/project/import-anywhere/
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-02-24 16:11:58