Jak wykonać import względny w Pythonie?

527

Wyobraź sobie tę strukturę katalogów:

app/
   __init__.py
   sub1/
      __init__.py
      mod1.py
   sub2/
      __init__.py
      mod2.py

Koduję mod1i muszę coś zaimportować mod2. Jak mam to zrobić?

Próbowałem, from ..sub2 import mod2ale otrzymuję komunikat „Próba względnego importu w pakiecie innym niż pakiet”.

Przeszukiwałem go, ale znalazłem tylko sys.pathhacki „ manipulacyjne”. Czy nie ma czystego sposobu?


Edycja: wszystkie moje __init__.pysą obecnie puste

Edit2: Staram się to zrobić, ponieważ sub2 zawiera klasy, które są wspólne dla pakietów Sub ( sub1, subXetc.).

Edycja3: Zachowanie, którego szukam, jest takie samo, jak opisano w PEP 366 (dzięki John B)

Joril
źródło
8
Zalecam zaktualizowanie pytania, aby wyjaśnić, że opisujesz problem rozwiązany w PEP 366.
John B
2
To długie i wyczerpujące wyjaśnienie, ale sprawdź tutaj: stackoverflow.com/a/10713254/1267156 Odpowiedziałem na bardzo podobne pytanie. Miałem ten sam problem do ostatniej nocy.
Sevvy325
3
Dla tych, którzy chcą załadować moduł znajdujący się na dowolnej ścieżce, zobacz to: stackoverflow.com/questions/67631/…
Evgeni Sergeev
2
W powiązanej uwadze Python 3 zmieni domyślną obsługę importu na domyślną bezwzględną; względny import będzie musiał zostać wyraźnie określony.
Ross

Odpowiedzi:

337

Wydaje się, że wszyscy chcą powiedzieć ci, co powinieneś robić, a nie tylko odpowiedzieć na pytanie.

Problem polega na tym, że uruchamiasz moduł jako „__main__”, przekazując mod1.py jako argument do interpretera.

Od PEP 328 :

Względne importy używają atrybutu __name__ modułu, aby określić pozycję 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.

W Pythonie 2.6 dodają możliwość odwoływania się do modułów względem modułu głównego. PEP 366 opisuje zmianę.

Aktualizacja : Według Nicka Coghlana zalecaną alternatywą jest uruchomienie modułu wewnątrz pakietu za pomocą przełącznika -m.

John B.
źródło
2
Odpowiedź tutaj obejmuje bałagan z sys.path w każdym punkcie wejścia do twojego programu. To chyba jedyny sposób, aby to zrobić.
Nick Retallack
76
Zalecaną alternatywą jest uruchamianie modułów w paczkach za pomocą -mprzełącznika, zamiast bezpośredniego podawania ich nazwy pliku.
ncoghlan
127
Nie rozumiem: gdzie jest odpowiedź tutaj? Jak importować moduły w takiej strukturze katalogów?
Tom
27
@Tom: W tym przypadku zrobiłby to mod1 from sub2 import mod2. Następnie, aby uruchomić mod1 z poziomu aplikacji, wykonaj python -m sub1.mod1.
Xiong Chiamiov
11
@XiongChiamiov: czy to oznacza, że ​​nie możesz tego zrobić, jeśli Twój python jest osadzony w aplikacji, więc nie masz dostępu do przełączników wiersza poleceń Pythona?
LarsH
130

Oto rozwiązanie, które działa dla mnie:

Wykonuję import względny jak from ..sub2 import mod2 i wtedy, jeśli chcę uruchomić, mod1.pyto idę do katalogu nadrzędnego appi uruchamiam moduł za pomocą przełącznika python -m as python -m app.sub1.mod1.

Prawdziwym powodem, dla którego ten problem występuje w przypadku importu względnego, jest fakt, że import względny działa, przejmując __name__właściwość modułu. Jeśli moduł jest uruchamiany bezpośrednio, to __name__jest ustawiony na __main__i nie zawiera żadnych informacji o strukturze pakietu. I dlatego python narzeka na relative import in non-packagebłąd.

Tak więc, używając przełącznika -m, podajesz pythonowi informacje o strukturze pakietu, dzięki którym może on pomyślnie rozwiązać import względny.

Problem ten napotkałem wiele razy podczas importowania względnego. Po przeczytaniu wszystkich poprzednich odpowiedzi nadal nie byłem w stanie wymyślić, jak to rozwiązać, w czysty sposób, bez konieczności umieszczania kodu podstawowego we wszystkich plikach. (Chociaż niektóre komentarze były bardzo pomocne, dzięki @ncoghlan i @XiongChiamiov)

Mam nadzieję, że pomoże to komuś, kto walczy z problemem importu względnego, ponieważ przechodzenie przez PEP nie jest naprawdę zabawne.

Pankaj
źródło
9
Najlepsza odpowiedź IMHO: nie tylko wyjaśnia, dlaczego OP miał problem, ale także znajduje sposób na jego rozwiązanie bez zmiany sposobu importowania jego modułów . W końcu względny import OP był w porządku. Winowajcą był brak dostępu do zewnętrznych pakietów podczas bezpośredniego uruchamiania jako skrypt, coś -mzostało zaprojektowane do rozwiązania.
MestreLion,
26
Zwróć też uwagę: ta odpowiedź była 5 lat po pytaniu. Funkcje te nie były wówczas dostępne.
JeremyKun
1
Jeśli chcesz zaimportować moduł z tego samego katalogu, możesz to zrobić from . import some_module.
Rotareti
124
main.py
setup.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       module_a.py
    package_b/ ->
       __init__.py
       module_b.py
  1. Ty biegniesz python main.py.
  2. main.py robi: import app.package_a.module_a
  3. module_a.py robi import app.package_b.module_b

Alternatywnie 2 lub 3 mogą użyć: from app.package_a import module_a

Będzie to działać tak długo, jak długo masz appw swojej PIĄTCE ŚCIEŻKI. main.pymoże być gdziekolwiek.

Więc piszesz, setup.pyaby skopiować (zainstalować) cały pakiet aplikacji i podpakiety do folderów python main.pysystemu docelowego i do folderów skryptów systemu docelowego.

nosklo
źródło
3
Doskonała odpowiedź. Czy istnieje jakiś sposób na zaimportowanie w ten sposób bez instalowania pakietu w PYTHONPATH?
auraham
4
Sugerowana dodatkowa lektura: blog.habnab.it/blog/2013/07/21/python-packages-and-you
nosklo
6
pewnego dnia musisz zmienić nazwę aplikacji na test_app. co by się stało? Musisz zmienić wszystkie kody źródłowe, zaimportować app.package_b.module_b -> test_app.package_b.module_b. jest to absolutnie ZŁA praktyka ... I powinniśmy spróbować użyć importu względnego w pakiecie.
Spybdai,
49

„Guido postrzega uruchamianie skryptów w pakiecie jako anty-wzór” (odrzucony PEP-3122 )

Spędziłem tyle czasu próbując znaleźć rozwiązanie, czytając podobne posty tutaj na Stack Overflow i mówiąc sobie: „musi być lepszy sposób!”. Wygląda na to, że nie ma.

lesnik
źródło
10
Uwaga: Wspomniany już pep-366 (utworzony mniej więcej w tym samym czasie co pep-3122 ) zapewnia te same możliwości, ale używa innej implementacji kompatybilnej wstecz, tj. Jeśli chcesz uruchomić moduł wewnątrz pakietu jako skrypt i użyć jawnego importu względnego w nim możesz uruchomić go za pomocą -mswitch: python -m app.sub1.mod1lub wywołać app.sub1.mod1.main()ze skryptu najwyższego poziomu (np. wygenerowanego z punktów wejścia setuptools zdefiniowanych w setup.py).
jfs
+1 za używanie setuptools i punktów wejścia - jest to właściwy sposób na skonfigurowanie skryptów, które będą uruchamiane z zewnątrz, w dobrze określonej lokalizacji, w przeciwieństwie do niekończącego się hakowania PYTHONPATH
RecencyEffect
38

Zostało to rozwiązane w 100%:

  • aplikacja /
    • main.py
  • ustawienia /
    • local_setings.py

Zaimportuj ustawienia / local_setting.py do app / main.py:

main.py:

import sys
sys.path.insert(0, "../settings")


try:
    from local_settings import *
except ImportError:
    print('No Import')
Роман Арсеньев
źródło
2
Dziękuję Ci! wszyscy ppl zmuszali mnie do innego uruchamiania skryptu, zamiast mówić mi, jak go rozwiązać w skrypcie. Ale musiałem zmienić kod, aby go użyć, sys.path.insert(0, "../settings")a następniefrom local_settings import *
Vit Bernatik
25
def import_path(fullpath):
    """ 
    Import a file with full path specification. Allows one to
    import from anywhere, something __import__ does not do. 
    """
    path, filename = os.path.split(fullpath)
    filename, ext = os.path.splitext(filename)
    sys.path.append(path)
    module = __import__(filename)
    reload(module) # Might be out of date
    del sys.path[-1]
    return module

Używam tego fragmentu do importowania modułów ze ścieżek, mam nadzieję, że to pomoże

iElectric
źródło
2
Używam tego fragmentu kodu w połączeniu z modułem imp (jak wyjaśniono tutaj [1]), aby uzyskać doskonały efekt. [1]: stackoverflow.com/questions/1096216/…
Xiong Chiamiov
7
Prawdopodobnie sys.path.append (ścieżka) należy zastąpić sys.path.insert (0, ścieżka), a sys.path [-1] należy zastąpić sys.path [0]. W przeciwnym razie funkcja zaimportuje niewłaściwy moduł, jeśli w ścieżce wyszukiwania jest już moduł o tej samej nazwie. Na przykład, jeśli w bieżącym katalogu znajduje się „some.py”, ścieżka_importu („/ Import / some.py”) zaimportuje niewłaściwy plik.
Alex Che
Zgadzam się! Czasami pierwszeństwo mają inne importy względne. Użyj sys.path.insert
iElectric
Jak zreplikowałbyś zachowanie z x import y (lub *)?
levesque,
Nie jest jasne, proszę podać pełne wykorzystanie tego skryptu, aby rozwiązać problem OP.
mrgloom
21

wyjaśnienie nosklo'sodpowiedzi z przykładami

Uwaga: wszystkie __init__.pypliki są puste.

main.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       fun_a.py
    package_b/ ->
       __init__.py
       fun_b.py

app / package_a / fun_a.py

def print_a():
    print 'This is a function in dir package_a'

app / package_b / fun_b.py

from app.package_a.fun_a import print_a
def print_b():
    print 'This is a function in dir package_b'
    print 'going to call a function in dir package_a'
    print '-'*30
    print_a()

main.py

from app.package_b import fun_b
fun_b.print_b()

po uruchomieniu $ python main.pyzwraca:

This is a function in dir package_b
going to call a function in dir package_a
------------------------------
This is a function in dir package_a
  • main.py wykonuje: from app.package_b import fun_b
  • fun_b.py robi from app.package_a.fun_a import print_a

więc plik w folderze package_bużywał pliku w folderze package_a, co jest tym, czego chcesz. Dobrze??

suhailvs
źródło
12

Jest to niestety hack sys.path, ale działa całkiem dobrze.

Problem napotkałem na innej warstwie: miałem już moduł o określonej nazwie, ale był to niewłaściwy moduł.

chciałem to zrobić (moduł, z którego pracowałem, to moduł3):

mymodule\
   __init__.py
   mymodule1\
      __init__.py
      mymodule1_1
   mymodule2\
      __init__.py
      mymodule2_1


import mymodule.mymodule1.mymodule1_1  

Zauważ, że już zainstalowałem mymoduł, ale w mojej instalacji nie mam „mymoduł1”

i dostałbym błąd ImportError, ponieważ próbował zaimportować z moich zainstalowanych modułów.

Próbowałem zrobić sys.path.append, ale to nie zadziałało. To, co zadziałało, to sys.path.insert

if __name__ == '__main__':
    sys.path.insert(0, '../..')

Cóż za hack, ale wszystko działało! Pamiętaj więc, że jeśli chcesz zastąpić inne ścieżki , musisz użyć sys.path.insert (0, nazwa ścieżki), aby działało! To było dla mnie bardzo frustrujące, wiele osób twierdzi, że używa funkcji „append” do sys.path, ale to nie działa, jeśli masz już zdefiniowany moduł (uważam to za bardzo dziwne zachowanie)

Garrett Berg
źródło
sys.path.append('../')działa dobrze dla mnie (Python 3.5.2)
Nister
Myślę, że jest to w porządku, ponieważ lokalizuje włamanie do pliku wykonywalnego i nie wpływa na inne moduły, które mogą zależeć od twoich pakietów.
Tom Russell,
10

Pozwól mi po prostu umieścić to tutaj dla własnego odniesienia. Wiem, że to nie jest dobry kod Pythona, ale potrzebowałem skryptu dla projektu, nad którym pracowałem i chciałem umieścić skrypt w scriptskatalogu.

import os.path
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
mleczarz
źródło
9

Jak mówi @EvgeniSergeev w komentarzach do OP, możesz importować kod z .pypliku w dowolnym miejscu za pomocą:

import imp

foo = imp.load_source('module.name', '/path/to/file.py')
foo.MyClass()

To pochodzi z tej SO odpowiedzi .

LondonRob
źródło
2

Z dokumentu Python ,

W Python 2.5 możesz zmienić zachowanie importu na import bezwzględny za pomocą from __future__ import absolute_importdyrektywy. To zachowanie bezwzględnego importu stanie się domyślnym w przyszłej wersji (prawdopodobnie Python 2.7). Gdy import bezwzględny jest domyślny, import stringzawsze znajdzie standardową wersję biblioteki. Sugeruje się, aby użytkownicy zaczęli korzystać z importów bezwzględnych w jak największym stopniu, dlatego lepiej zacząć pisać from pkg import stringw kodzie

jung rhew
źródło
1

Odkryłem, że łatwiej jest ustawić zmienną środowiska „PYTHONPATH” w górnym folderze:

bash$ export PYTHONPATH=/PATH/TO/APP

następnie:

import sub1.func1
#...more import

oczywiście PYTHONPATH jest „globalny”, ale nie przysporzył mi jeszcze problemów.

Andrew_1510
źródło
Zasadniczo virtualenvpozwala to zarządzać wyciągami z importu.
byxor
1

Oprócz tego, co powiedział John B, wydaje się, że ustawienie __package__zmiennej powinno pomóc, zamiast zmieniać__main__ co mogłoby popsuć inne rzeczy. Ale o ile mogłem przetestować, nie działa tak, jak powinien.

Mam ten sam problem i ani PEP 328, ani 366 nie rozwiązują go całkowicie, ponieważ do końca dnia oba wymagają dołączenia głowy pakietu sys.path , o ile rozumiem.

Powinienem również wspomnieć, że nie znalazłem sposobu na sformatowanie ciągu, który powinien przejść do tych zmiennych. Czy to "package_head.subfolder.module_name"czy co?

Gabriel
źródło
0

Musisz dołączyć ścieżkę modułu do PYTHONPATH:

export PYTHONPATH="${PYTHONPATH}:/path/to/your/module/"
Giorgos Myrianthous
źródło
1
Jest to mniej więcej to samo, co manipulowanie sys.path, ponieważ sys.pathzostaje zainicjowane zPYTHONPATH
Joril
@Joril To prawda, ale sys.pathmusi być zakodowana na stałe w kodzie źródłowym, w przeciwieństwie do tego, PYTHONPATHktóra jest zmienną środowiskową i może być eksportowana.
Giorgos Myrianthous