Względny import po raz miliardowy

716

Byłem tu:

i mnóstwo adresów URL, których nie skopiowałem, niektóre na SO, inne na innych stronach, kiedy myślałem, że szybko znajdę rozwiązanie.

Zawsze powtarzające się pytanie brzmi: w systemie Windows 7, 32-bitowym Pythonie 2.7.3, jak rozwiązać ten komunikat „Próba względnego importu w pakiecie innym niż pakiet”? Zbudowałem dokładną replikę pakietu na pep-0328:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

Importy zostały wykonane z konsoli.

Zrobiłem funkcje o nazwie spam i jaja w odpowiednich modułach. Oczywiście to nie działało. Odpowiedź znajduje się w czwartym podanym adresie URL, ale dla mnie to wszystko jest absolwentami. Odpowiedź pojawiła się na jednym z adresów URL, które odwiedziłem:

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.

Powyższa odpowiedź wygląda obiecująco, ale dla mnie wszystkie hieroglify. Więc moje pytanie, jak sprawić, aby Python nie zwrócił mi „Próba importu względnego w pakiecie innym niż pakiet”? ma odpowiedź, która zawiera rzekomo -m.

Czy ktoś może mi powiedzieć, dlaczego Python wyświetla ten komunikat o błędzie, co to znaczy „nieopakowany”, dlaczego i jak definiuje się „pakiet”, a dokładna odpowiedź jest wystarczająco łatwa do zrozumienia dla przedszkola .

Paul Roub
źródło
5
Jak próbujesz użyć wyświetlanych plików? Jakiego kodu używasz?
BrenBarn
Zobacz python.org/dev/peps/pep-0328 . Użyłem formatu paczki opisanego w moim poście. W startowych pliki .py są puste. moduleY.py ma def spam(): pass, moduleA.py ma def eggs(): pass. Próbowałem wykonać kilka poleceń „z. Coś importować coś”, ale one nie działały. Ponownie, patrz pep-0328.
6
Zobacz moją odpowiedź. Nadal nie wyjaśniłeś dokładnie, co robisz, ale jeśli próbujesz to zrobić from .something import somethingw interaktywnym tłumaczu, to nie zadziała. Względnego importu można używać tylko wewnątrz modułów, a nie interaktywnie.
BrenBarn
105
Sam fakt, że „miliardy” ludzi - ok. 83 136 z tego komentarza - mają wystarczającą trudność z importem, aby znaleźć to pytanie; możemy jedynie stwierdzić, że importowanie Pythona jest sprzeczne z intuicją dla wielu, jeśli nie dla większości programistów. Guido, być może powinieneś to zaakceptować i poprosić komitet o przeprojektowanie mechanizmu importu. Ta składnia powinna przynajmniej działać, jeśli x.py i z.py znajdują się w tym samym katalogu. Mianowicie, jeśli x.py ma instrukcję: „z .z import MyZebraClass” x powinien zaimportować z NAWET, jeśli jest uruchamiany jako główny ! Dlaczego to takie trudne?
Steve L.
4
Po przeczytaniu dużej części tego wątku, chociaż nie jest to odpowiedź na pytanie, „po prostu użyj importu bezwzględnego” wydaje się rozwiązaniem ...
CodeJockey

Odpowiedzi:

1042

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.pyw wierszu poleceń. Jest ładowany jako moduł, jeśli tak python -m myfile, lub jeśli jest ładowany, gdy importinstrukcja 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 moduleAz 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/subpackage1a następnie zrobisz import moduleX, nazwa po moduleXprostu 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 pythoni 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 ..moduleAznaczy package.moduleA. Aby a from .. importdziałał, nazwa modułu musi zawierać co najmniej tyle kropek, ile jest w importinstrukcji.

... 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 .. importinstrukcji. 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 moduleXitp. 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:

  1. Jeśli naprawdę chcesz uruchomić moduleXbezpośrednio, ale nadal chcesz, aby był traktowany jako część pakietu, możesz to zrobić python -m package.subpackage1.moduleX. -mMówi Python ładować go jako moduł, a nie jak w skrypcie najwyższego poziomu.

  2. 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 packagekatalogu - i uruchom go. Jeśli w środku myfile.pyrobisz takie rzeczy from package.moduleA import spam, będzie dobrze.

Notatki

  • Dla każdego z tych rozwiązań katalog pakietu ( packagew 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.)

BrenBarn
źródło
62
Its name has no dots, and therefore you cannot use from .. import statements inside it. If you try to do so, you will get the "relative-import in non-package" error.Jest to zasadniczo niepokojące. Co jest takiego trudnego w przeglądaniu bieżącego katalogu? Python powinien mieć taką możliwość. Czy to naprawiono w wersji 3x?
7
@Stopforgettingmyaccounts ...: PEP 366 pokazuje, jak to działa. Wewnątrz pliku możesz zrobić coś __package__ = 'package.subpackage1'podobnego. Wtedy tylko ten plik będzie zawsze uważany za część tego pakietu, nawet jeśli zostanie uruchomiony bezpośrednio. Jeśli masz inne pytania, __package__możesz zadać osobne pytanie, ponieważ omawiamy tutaj oryginalne pytanie.
BrenBarn
108
To powinna być odpowiedź na wszystkie pytania dotyczące importu względnego Pythona. Powinno to być nawet w dokumentacji.
edsioufi
10
Zobacz python.org/dev/peps/pep-0366 - „Zauważ, że ten szablon jest wystarczający tylko wtedy, gdy pakiet najwyższego poziomu jest już dostępny poprzez sys.path. W celu bezpośredniego wykonania potrzebny byłby dodatkowy kod, który manipuluje sys.path. do pracy bez możliwości importowania pakietu najwyższego poziomu. ” - jest to dla mnie najbardziej niepokojące, ponieważ ten „dodatkowy kod” jest w rzeczywistości dość długi i nie można go przechowywać w innym miejscu pakietu, aby można go było łatwo uruchomić.
Michael Scott Cuthbert
14
Ta odpowiedź jest obecnie wyłączona w kilku ważnych szczegółach dotyczących __name__i sys.path. W szczególności, z python -m pkg.mod, __name__jest ustawiony na __main__nie pkg.mod; import względny jest rozstrzygany za pomocą __package__raczej niż __name__w tym przypadku. Ponadto Python dodaje do katalogu katalog skryptu, a nie katalog sys.pathbieżący python path/to/script.py; dodaje bieżący katalog do sys.pathwiększości innych sposobów, w tym python -m pkg.mod.
user2357112 obsługuje Monikę
42

To jest naprawdę problem w Pythonie. Przyczyną zamieszania jest to, że ludzie błędnie przyjmują relatywny import jako relatywną ścieżkę, która nie jest.

Na przykład, gdy piszesz w faa.py :

from .. import foo

Ma to znaczenie tylko wtedy, gdy plik faa.py został zidentyfikowany i załadowany przez python podczas wykonywania, jako część pakietu. W takim przypadku nazwą modułu dla faa.py będzie na przykład nazwa_pakietu.faa . Jeśli plik został załadowany tylko dlatego, że znajduje się w bieżącym katalogu, po uruchomieniu Pythona jego nazwa nie odnosiłaby się do żadnego pakietu i ostatecznie import względny nie powiódłby się.

Prostym rozwiązaniem do odsyłania 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
Rami Ka.
źródło
5
Prawidłowe rozwiązanie jest from __future__ import absolute_importi zmusza użytkownika do prawidłowego używania kodu ... abyś zawsze mógł to zrobićfrom . import foo
Giacomo Alzetta
@Giacomo: absolutnie właściwa odpowiedź na mój problem. Dzięki!
Fábio
8

Oto ogólny przepis, zmodyfikowany tak, aby pasował jako przykład, którego używam teraz do pracy z bibliotekami Pythona napisanymi jako pakiety, które zawierają współzależne pliki, w których chcę móc testować ich fragmentaryczne fragmenty. Nazwijmy to lib.fooi powiedzieć, że to musi mieć dostęp do lib.fileAdo funkcji f1i f2, a lib.fileBdo klasy Class3.

Dołączyłem kilka printtelefonów, aby zilustrować, jak to działa. W praktyce chciałbyś je usunąć (a może i from __future__ import print_functionlinię).

Ten konkretny przykład jest zbyt prosty, aby pokazać, kiedy naprawdę musimy wstawić wpis sys.path. (Zobacz odpowiedź Larsa na przypadek, w którym jej 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 to też tak naprawdę nie boli .) Jest to również wystarczająco bezpieczne, aby to zrobić bez if _i in sys.pathtest. Jednak jeśli każdy zaimportowany plik wstawi tę samą ścieżkę - na przykład, jeśli obie fileAi fileBchcą zaimportować narzędzia z pakietu - zaśmieca sys.pathto wiele razy tę samą ścieżkę, więc dobrze jest mieć ją if _i not in sys.pathna płycie głównej.

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)

Chodzi o to (i zauważ, że wszystkie one działają tak samo w python2.7 i python 3.x):

  1. Jeśli uruchamiane jako import liblub from lib import foojako zwykły zaimportować pakiet ze zwykłym kodzie, __packageto libi __name__to lib.foo. Bierzemy pierwszą ścieżkę kodu, importujemy z .fileAitd.

  2. Jeśli zostanie uruchomiony jako python lib/foo.py, __package__będzie Brak i __name__będzie __main__.

    Bierzemy drugą ścieżkę kodu. libKatalog będzie już w sys.pathwięc nie ma potrzeby, aby go dodać. Importujemy z fileAitp.

  3. Jeśli zostanie uruchomiony w libkatalogu as python foo.py, zachowanie jest takie samo jak w przypadku 2.

  4. Jeśli jest uruchamiany w libkatalogu as python -m foo, zachowanie jest podobne do przypadków 2 i 3. Jednak ścieżka do libkatalogu nie istnieje sys.path, więc dodajemy go przed importowaniem. To samo dotyczy, jeśli uruchomimy Python, a następnie import foo.

    (Ponieważ . jest na początku sys.path, tak naprawdę nie musimy tutaj dodawać absolutnej wersji ścieżki. W tym miejscu głębsza struktura zagnieżdżania pakietów, tam gdzie chcemy to zrobić from ..otherlib.fileC import ..., robi różnicę. Jeśli tego nie robisz, możesz sys.pathcałkowicie pomiń całą manipulację).

Notatki

Nadal istnieje dziwactwo. Jeśli uruchomisz to wszystko z zewnątrz:

$ python2 lib.foo

lub:

$ python3 lib.foo

zachowanie zależy od zawartości lib/__init__.py. Jeśli to istnieje i jest puste , wszystko jest dobrze:

Package named 'lib'; __name__ is '__main__'

Ale jeśli lib/__init__.py sam importuje, routineaby można było eksportować routine.namebezpośrednio jako lib.name, otrzymasz:

$ python2 lib.foo
Package named 'lib'; __name__ is 'lib.foo'
Package named 'lib'; __name__ is '__main__'

Oznacza to, że moduł jest importowany dwukrotnie, raz za pośrednictwem pakietu, a następnie ponownie __main__, aby uruchomić mainkod. Python 3.6 i nowsze wersje 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__'

Ostrzeżenie jest nowy, ale ostrzegł, o zachowanie nie jest. Jest to część tego, co niektórzy nazywają pułapką podwójnego importu . (Aby uzyskać dodatkowe informacje, patrz problem 27487 ). Nick Coghlan mówi:

Ta kolejna pułapka istnieje we wszystkich bieżących wersjach Pythona, w tym w wersji 3.3, i można ją streścić w następujących ogólnych wskazówkach: „Nigdy nie dodawaj katalogu pakietu ani żadnego katalogu wewnątrz pakietu bezpośrednio do ścieżki Pythona”.

Pamiętaj, że chociaż naruszamy tę zasadę tutaj, robimy to tylko wtedy, gdy ładowany plik nie jest ładowany jako część pakietu, a nasza modyfikacja została zaprojektowana specjalnie, aby umożliwić nam dostęp do innych plików w tym pakiecie. (I, jak zauważyłem, prawdopodobnie nie powinniśmy tego robić w przypadku pakietów jednopoziomowych.) Jeśli chcielibyśmy być wyjątkowo czysti, moglibyśmy przepisać to jako 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.pathwystarczająco długo, aby osiągnąć nasz import, a następnie ustawiamy go z powrotem tak, jak było (usuwając jedną kopię, _ijeśli i tylko jeśli dodaliśmy jedną kopię _i).

Trek
źródło
7

Po zastanowieniu się nad tym wraz z wieloma innymi, natknąłem się na notatkę opublikowaną przez Doriana B w tym artykule, która rozwiązała konkretny problem, który miałem, gdzie opracowywałem moduły i klasy do użytku z usługą internetową, ale chcę też być mogłem je testować podczas pisania kodu, korzystając z narzędzi do debugowania w PyCharm. Aby uruchomić testy w samodzielnej klasie, na końcu mojego pliku klasy umieściłbym następujące:

if __name__ == '__main__':
   # run test code here...

ale gdybym chciał zaimportować inne klasy lub moduły w tym samym folderze, musiałbym wtedy zmienić wszystkie moje instrukcje importu z notacji względnej na referencje lokalne (tj. usunąć kropkę (.)) Ale po przeczytaniu sugestii Doriana spróbowałem jego „ one-liner ”i zadziałało! Mogę teraz testować w PyCharm i pozostawić mój kod testowy na miejscu, gdy korzystam z klasy w innej testowanej klasie lub kiedy używam jej 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 korzystamy z tego modułu jako głównego, czy też jest używany w innym module, który jest testowany jako główny . 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ć.

Steve L.
źródło
1
To faktycznie rozwiązuje problem. Ale to jest naprawdę paskudne. Dlaczego nie jest to zachowanie domyślne ?!
lo tolmencre
4

Oto jedno rozwiązanie, którego nie poleciłbym, 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()
Federico
źródło
2

Miałem podobny problem, w którym nie chciałem zmieniać ścieżki wyszukiwania modułów Pythona i musiałem ładować moduł stosunkowo ze skryptu (pomimo „skryptów nie można zaimportować względnie wszystkich”, jak BrenBarn ładnie wyjaśnił powyżej).

Więc użyłem następującego hacka. Niestety, opiera się on na impmodule, który stał się przestarzały od wersji 3.4, która została odrzucona na korzyść importlib. (Czy to też jest możliwe importlib? Nie wiem.) Na razie hack działa.

Przykład uzyskiwania dostępu do członków moduleXw subpackage1ze skryptu znajdującego się w subpackage2folderze:

#!/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

Wydaje się, że bardziej przejrzystym podejściem jest zmodyfikowanie sys.path używanego do ładowania modułów, o czym 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 *
Lars
źródło
To wygląda lepiej ... szkoda, że ​​nadal wymaga osadzenia nazwy katalogu nadrzędnego w pliku ... może to można poprawić za pomocą importlib. Może importlib może być nawet małpowany, aby import względny „po prostu działał” w prostych przypadkach użycia. Zrobię to.
Andrew Wagner
Używam jednak Pythona 2.7.14. Czy coś takiego nadal będzie działać?
user3474042
Właśnie przetestowałem oba podejścia na Pythonie 2.7.10 i działały one dla mnie dobrze. Jeśli tak, to nie masz problemu z przestarzałym modułem imp w 2.7, więc tym lepiej.
Lars
2

__name__ zmienia się w zależności od tego, czy dany kod jest uruchamiany w globalnej przestrzeni nazw, czy jako część importowanego modułu.

Jeśli kod nie działa w przestrzeni globalnej, __name__będzie to nazwa modułu. Jeśli jest uruchomiony w globalnej przestrzeni nazw - na przykład, jeśli wpiszesz go w konsoli lub uruchom moduł jako skrypt używający python.exe yourscriptnamehere.pywtedy, to __name__staje się "__main__".

Zobaczysz dużo kodu Pythona if __name__ == '__main__'używanego do testowania, czy kod jest uruchamiany z globalnej przestrzeni nazw - co pozwala mieć moduł, który może również pełnić funkcję skryptu.

Czy próbowałeś wykonać te importy z konsoli?

theodox
źródło
Ach, więc wspominasz -m. To powoduje, że moduł wykonuje się jako skrypt - jeśli włożysz tam if __name__ == '__main__', powinieneś zobaczyć, że jest to '__main__' z powodu -m. Spróbuj po prostu zaimportować moduł do innego modułu, aby nie był to najwyższy poziom ... co powinno pozwolić na wykonanie importu względnego
theodox
Próbowałem wykonać te importy z konsoli, a aktywny plik był poprawnym modułem.
@Stopforgettingmyaccounts ...: Co rozumiesz przez „aktywny plik”?
BrenBarn
Używam Pyscriptera. Byłem w moduleX.py, kiedy uruchomiłem te importowanie: z .moduleY importuj spam i z. importuj modułY.
Nie importować .moduleY, po którym następuje moduleY.spam ()?
theodox
2

Odpowiedź BrenBarn mówi wszystko, ale jeśli jesteś podobny do mnie, zrozumienie może trochę potrwać. Oto moja sprawa i jak odpowiedź @ BrenBarn ma do niej zastosowanie, być może pomoże ci.

Walizka

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

Korzystając z naszego znanego przykładu i dodaj do niego, że moduleX.py ma względny import do ..moduleA. Biorąc pod uwagę, że próbowałem napisać skrypt testowy w katalogu subpackage1, który zaimportował moduł X, ale potem dostałem przerażający błąd opisany przez OP.

Rozwiązanie

Przenieś skrypt testowy na ten sam poziom co pakiet i zaimportuj pakiet.subpackage1.moduleX

Wyjaśnienie

Jak wyjaśniono, import względny jest dokonywany względem bieżącej nazwy. Kiedy mój skrypt testowy importuje moduł X z tego samego katalogu, nazwa modułu wewnątrz modułu X to moduł X. Gdy napotka import względny, interpreter nie może wykonać kopii zapasowej hierarchii pakietów, ponieważ jest już na górze

Kiedy importuję moduł X z góry, wówczas nazwa wewnątrz modułu X to pakiet.subpackage1.moduleX i można znaleźć import względny

Brad Dre
źródło
Mam nadzieję, że możesz mi w tym pomóc. W poniższym linku, jeśli przejdziesz do przypadku 3, napisano, że rozwiązanie 1 nie jest możliwe. Czy możesz to sprawdzić i dać mi znać. Ogromnie mi to pomoże. chrisyeh96.github.io/2017/08/08/…
zmienna
@variable w linku jest literówka i nie mogę edytować. Przejrzałem przypadek 3 i nie podążył dokładnie za tym, do czego zmierzasz. Kiedy próbowałem tego przykładu w Pythonie 2, nie było żadnych problemów, które sugerują, że coś przeoczyłem. Może powinieneś opublikować nowe pytanie, ale musisz podać jaśniejszy przykład. Przypadek 4 dotyczy tego, o czym mówię w mojej odpowiedzi tutaj: nie można przejść do katalogu w celu względnego importu, chyba że tłumacz zaczyna od katalogu nadrzędnego
Brad Dre
Dzięki, odnoszę się do Pythona 3 i tutaj pytanie stackoverflow.com/questions/58577767/…
zmienna
1

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.

Napisałem mały pakiet Pythona do PyPi, który może pomóc widzom w tym pytaniu. Pakiet działa jako obejście, jeśli ktoś chce mieć możliwość uruchamiania plików Pythona zawierających importy zawierające pakiety wyższego poziomu z pakietu / projektu, nie będąc bezpośrednio w katalogu pliku importu. https://pypi.org/project/import-anywhere/

ec2604
źródło
-2

Aby Python nie wrócił do mnie „Podjęto próbę importu względnego w pakiecie innym niż pakiet”. pakiet/

init .py subpackage1 / init .py moduleX.py moduleY.py subpackage2 / init .py moduleZ.py moduleA.py

Ten błąd występuje tylko wtedy, gdy stosujesz import względny do pliku nadrzędnego. Na przykład plik nadrzędny już zwraca main po wpisaniu „print ( name )” w moduleA.py. Więc ten plik jest już głównynie może zwrócić żadnego pakietu nadrzędnego dalej. względne importy są wymagane w plikach pakietów subpackage1 i subpackage2, można użyć „..”, aby odnieść się do katalogu nadrzędnego lub modułu. Ale rodzic jest, jeśli już pakiet najwyższego poziomu nie może przejść dalej niż ten katalog nadrzędny (pakiet). Takie pliki, w których stosujesz import względny do rodziców, mogą działać tylko z zastosowaniem importu bezwzględnego. Jeśli użyjesz ABSOLUTNEGO IMPORTU W PAKIETIE RODZICIELSKIM, NIE BĘDZIE BŁĄD, ponieważ python wie, kto jest na najwyższym poziomie pakietu, nawet jeśli twój plik znajduje się w paczkach z powodu koncepcji PYTHON PATH, która definiuje najwyższy poziom projektu

Sakshi Jain
źródło