Względny import po raz miliardowy

Byłem tu:

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.

Author: Brian Burns, 2013-01-03

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:

  1. 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.

  2. A może nie właściwie chcę uruchomić moduleX, po prostu chcesz uruchomić jakiś inny skrypt, powiedzmy myfile.py, który używa funkcji wewnątrz moduleX. Jeśli tak jest, postaw myfile.py somewhere else --- Nie wewnątrz katalogu package -- i uruchom go. Jeśli wewnątrz myfile.py będziesz robił takie rzeczy jak from 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__ jest None.)

 559
Author: BrenBarn,
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
 8
Author: Rami Ka.,
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):

  1. Jeśli jest uruchamiany jako import lib lub from lib import foo jako zwykły import pakietów z zwykłego kodu, __package jest lib, A __name__ jest lib.foo. Wybieramy pierwszą ścieżkę kodu, importujemy z .fileA, itd.

  2. 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ż w sys.path, więc nie trzeba go dodawać. Importujemy z fileA, itp.

  3. Jeśli jest uruchamiany w katalogu lib jako python foo.py, zachowanie jest takie samo jak w przypadku przypadku 2.

  4. Jeśli jest uruchomiony w katalogu lib jako python -m foo, zachowanie jest podobne do przypadków 2 i 3. Jednak ścieżka do katalogu lib nie znajduje się w sys.path, więc dodaj go przed zaimportowaniem. To samo dotyczy Pythona, a następnie import foo.

    (od . jest w sys.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).

 2
Author: torek,
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()
 1
Author: Federico,
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?

 1
Author: theodox,
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 *
 0
Author: Lars,
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ć.

 0
Author: Steve L,
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/

 -1
Author: ec2604,
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