Okrągły (lub cykliczny) import do Pythona

351

Co się stanie, jeśli dwa moduły zaimportują się nawzajem?

Aby uogólnić problem, co z cyklicznymi importami w Pythonie?

Xolve
źródło
1
Zobacz także stackoverflow.com/questions/158268/…
Constantin
1
również jako odniesienie wydaje się, że cykliczny import jest dozwolony w Pythonie 3.5 (i prawdopodobnie poza nim), ale nie 3.4 (i prawdopodobnie poniżej).
Charlie Parker
4
Korzystam z Pythona 3.7.2 i nadal mam błąd w czasie wykonywania z powodu zależności cyklicznych.
Richard Whitehead

Odpowiedzi:

280

W zeszłym roku odbyła się naprawdę dobra dyskusja na comp.lang.python . Dość dokładnie odpowiada na twoje pytanie.

Importowanie jest naprawdę bardzo proste. Pamiętaj tylko, że:

„import” i „from xxx import yyy” są instrukcjami wykonywalnymi. Wykonują się, gdy uruchomiony program osiągnie ten wiersz.

Jeśli modułu nie ma w sys.modules, wówczas import tworzy nowy wpis modułu w sys.modules, a następnie wykonuje kod w module. Nie zwraca kontroli do modułu wywołującego, dopóki wykonanie nie zostanie zakończone.

Jeśli moduł istnieje w sys.modules, wówczas import po prostu zwraca ten moduł, niezależnie od tego, czy zakończył wykonywanie. Dlatego cykliczny import może zwracać moduły, które wydają się częściowo puste.

Na koniec skrypt wykonujący działa w module o nazwie __main__, importowanie skryptu pod własną nazwą spowoduje utworzenie nowego modułu niezwiązanego z __main__.

Weź to wszystko razem i nie powinieneś mieć żadnych niespodzianek podczas importowania modułów.

Shane C. Mason
źródło
13
@meawoppl Czy mógłbyś rozwinąć ten komentarz? Jak konkretnie się zmienili?
Dan Schien,
3
Na razie jedyne odniesienie do importu cyklicznego w python3 „Co nowego?” stron jest w wersji 3.5 . Napisano: „Obsługiwany jest teraz import okrągły związany z importem względnym”. @meawoppl znalazłeś coś jeszcze, czego nie ma na tych stronach?
zezollo 21.04.16
4
Oni są def. nieobsługiwany w 3.0-3.4. A przynajmniej semantyka sukcesu jest inna. Oto streszczenie, które znalazłem, nie wspominając o zmianach 3.5. gist.github.com/datagrok/40bf84d5870c41a77dc6
meawoppl
Proszę rozwinąć ten temat: „Na koniec skrypt wykonujący działa w module o nazwie main , importowanie skryptu pod własną nazwą spowoduje utworzenie nowego modułu niezwiązanego z main .”. Powiedzmy, że plik to a.py, a kiedy działa jako główny punkt wejścia, jest teraz głównym, jeśli ma kod jak z importu, jakaś zmienna. Czy ten sam plik „a.py” zostanie załadowany do tabeli modułów sys? Czy to oznacza, że ​​jeśli powie napis print, będzie działał dwukrotnie? Raz dla głównego pliku i ponownie po napotkaniu importu?
zmienna
Ta odpowiedź ma 10 lat i chciałbym zmodernizować aktualizację, aby upewnić się, że pozostanie poprawna w różnych wersjach Pythona, 2.x lub 3.x
Fallenreaper,
296

Jeśli zrobisz to import foowewnątrz bari import barwewnątrz foo, będzie działać dobrze. Do czasu, gdy coś faktycznie się uruchomi, oba moduły zostaną w pełni załadowane i będą miały odniesienia do siebie.

Problem polega na tym, kiedy zamiast tego robisz from foo import abci from bar import xyz. Ponieważ teraz każdy moduł wymaga, aby drugi moduł został już zaimportowany (aby nazwa, którą importujemy, istniała), zanim będzie można ją zaimportować.

user2357112 obsługuje Monikę
źródło
27
Wydaje się, że from foo import *i from bar import *również będzie działać dobrze.
Akavall,
1
Sprawdź edycję powyższego postu za pomocą a.py/b.py. Nie używa from x import y, a mimo to nadal pojawia się błąd importu cyklicznego
Greg Ennis
2
To nie do końca prawda. Podobnie jak w przypadku importowania * z, jeśli spróbujesz uzyskać dostęp do elementu w imporcie cyklicznym, na najwyższym poziomie, więc zanim skrypt zakończy wykonywanie, będziesz miał ten sam problem. Na przykład, jeśli ustawiasz pakiet globalny w jednym pakiecie z drugiego, a oba obejmują się nawzajem. Robiłem to, aby utworzyć niechlujną fabrykę dla obiektu w klasie bazowej, w którym ten obiekt mógłby być jedną z wielu podklas, a przy użyciu kodu nie trzeba było wiedzieć, który on faktycznie tworzy.
AaronM
3
@Akavall Nie bardzo. Spowoduje to jedynie zaimportowanie nazw dostępnych po wykonaniu importinstrukcji. Więc to nie spowoduje błędu, ale możesz nie uzyskać wszystkich oczekiwanych zmiennych.
sierpień
3
Uwaga: jeśli to zrobisz from foo import *i from bar import *wszystko wykonane w foojest w fazie inicjalizacji bar, a rzeczywiste funkcje w barnie zostały jeszcze zdefiniowane ...
Martian2049,
100

Cykliczne importowanie zostaje zakończone, ale należy uważać, aby nie używać cyklicznie importowanych modułów podczas inicjalizacji modułu.

Rozważ następujące pliki:

a.py:

print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"

b.py:

print "b in"
import a
print "b out"
x = 3

Jeśli wykonasz a.py, otrzymasz:

$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out

Podczas drugiego importu b.py (w drugim a in) interpreter Pythona nie importuje się bponownie, ponieważ już istnieje w module dict.

Jeśli próbujesz uzyskać dostęp b.xz apodczas inicjalizacji modułu dostaniesz AttributeError.

Dodaj następujący wiersz do a.py:

print b.x

Następnie dane wyjściowe to:

$ python a.py
a in                    
b imported: False
b in
a in
b imported: True
a out
Traceback (most recent call last):
  File "a.py", line 4, in <module>
    import b
  File "/home/shlomme/tmp/x/b.py", line 2, in <module>
    import a
 File "/home/shlomme/tmp/x/a.py", line 7, in <module>
    print b.x
AttributeError: 'module' object has no attribute 'x'

Wynika to z faktu, że moduły są wykonywane podczas importu i w momencie b.xdostępu do niego linia x = 3nie została jeszcze wykonana, co nastąpi dopiero po b out.

Torsten Marek
źródło
14
to doskonale wyjaśnia problem, ale co z rozwiązaniem? jak możemy poprawnie importować i drukować x inne powyższe rozwiązanie nie działało dla mnie
mehmet
Myślę, że odpowiedź byłaby dużo zyskać, jeśli stosowany __name__zamiast 'a'. Na początku byłem całkowicie zdezorientowany, dlaczego plik zostanie wykonany dwukrotnie.
Bergi
30

Jak inne odpowiedzi opisują ten wzorzec jest akceptowalny w pythonie:

def dostuff(self):
     from foo import bar
     ...

Co pozwoli uniknąć wykonywania instrukcji importu, gdy plik zostanie zaimportowany przez inne moduły. Tylko jeśli istnieje logiczna zależność cykliczna, to się nie powiedzie.

Większość importów cyklicznych nie jest w rzeczywistości logicznymi importami cyklicznymi, ale powoduje ImportErrorbłędy, z powodu sposobu, w jaki import()wywoływane są instrukcje najwyższego poziomu całego pliku.

Te ImportErrorsmogą być prawie zawsze uniknąć, jeśli chcesz, aby import pozytywnie na górze :

Rozważ ten okrągły import:

Aplikacja A

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)

Aplikacja B

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

David Beazleys znakomita rozmowa Moduły i pakiety: Live and Let Die! - PyCon 2015 , 1:54:00oto sposób radzenia sobie z okrągłym importem w pythonie:

try:
    from images.serializers import SimplifiedImageSerializer
except ImportError:
    import sys
    SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']

Spróbuje zaimportować SimplifiedImageSerializeri jeśli ImportErrorzostanie podniesiony, ponieważ jest już zaimportowany, wyciągnie go z importcache.

PS: Musisz przeczytać cały post głosem Davida Beazleya.

Sebastian Woźny
źródło
9
Błąd ImportError nie jest wywoływany, jeśli moduł został już zaimportowany. Moduły można importować dowolną liczbę razy, np. „Importuj a; importuj a” jest ok
Yuras
9

Mam tutaj przykład, który mnie uderzył!

foo.py

import bar

class gX(object):
    g = 10

bar.py

from foo import gX

o = gX()

main.py

import foo
import bar

print "all done"

W wierszu polecenia: $ python main.py

Traceback (most recent call last):
  File "m.py", line 1, in <module>
    import foo
  File "/home/xolve/foo.py", line 1, in <module>
    import bar
  File "/home/xolve/bar.py", line 1, in <module>
    from foo import gX
ImportError: cannot import name gX
Xolve
źródło
2
Jak to naprawiłeś? Próbuję zrozumieć cykliczny import, aby rozwiązać mój własny problem, który wygląda bardzo podobnie do tego, co robisz ...
c089,
12
Errm ... Myślę, że naprawiłem swój problem z tym niesamowicie brzydkim hakiem. {{{jeśli nie „foo.bar” ​​w sys.modules: z paska importu foo else: bar = sys.modules ['foo.bar']}}} Osobiście uważam, że okrągły import jest OGROMNYM znakiem ostrzegawczym na złym kodzie projekt ...
c089,
5
@ c089, czy można po prostu przenieść import barsię foo.pydo końcowego
warvariuc
5
Jeśli bari foooba muszą użyć gX, „najczystszym” rozwiązaniem jest umieszczenie gXinnego modułu, posiadanie obu fooi barimportowanie tego modułu. (najczystszy w tym sensie, że nie ma ukrytych zależności semantycznych).
Tim Wilder,
2
Tim ma rację. Zasadniczo to dlatego, że barnie można nawet znaleźć gXw foo. cykliczny import sam w sobie jest w porządku, ale po prostu gXnie jest definiowany podczas importowania.
Martian2049,
9

Moduł a.py:

import b
print("This is from module a")

Moduł b.py

import a
print("This is from module b")

Uruchomienie „modułu a” spowoduje:

>>> 
'This is from module a'
'This is from module b'
'This is from module a'
>>> 

Wyprowadza te 3 wiersze, podczas gdy miał generować nieskończoność z powodu importu cyklicznego. Co dzieje się linia po linii podczas uruchamiania „Modułu a” jest wymienione tutaj:

  1. Pierwsza linia to import b. więc odwiedzi moduł b
  2. Pierwsza linia w module b to import a. więc odwiedzi moduł a
  3. Pierwsza linia w module a to, import bale pamiętaj, że ta linia nie będzie już więcej wykonywana , ponieważ każdy plik w pythonie wykonuje wiersz importu tylko raz, nie ma znaczenia, gdzie i kiedy jest wykonywany. więc przejdzie do następnej linii i wydrukuje "This is from module a".
  4. Po zakończeniu wizyty w całym module a z modułu b nadal jesteśmy w module b. więc zostanie wydrukowany następny wiersz"This is from module b"
  5. Linie modułu b są wykonywane całkowicie. więc wrócimy do modułu a, w którym uruchomiliśmy moduł b.
  6. linia importu b została już wykonana i nie zostanie wykonana ponownie. zostanie wydrukowany następny wiersz "This is from module a"i program zostanie zakończony.
Mohsen Haddadi
źródło
4

Całkowicie zgadzam się z odpowiedzią pytającego tutaj. Ale natknąłem się na kod, który był wadliwy przy okrągłym imporcie i powodował problemy podczas próby dodania testów jednostkowych. Aby szybko go załatać bez zmiany wszystkiego, możesz rozwiązać problem, wykonując import dynamiczny.

# Hack to import something without circular import issue
def load_module(name):
    """Load module using imp.find_module"""
    names = name.split(".")
    path = None
    for name in names:
        f, path, info = imp.find_module(name, path)
        path = [path]
    return imp.load_module(name, f, path[0], info)
constants = load_module("app.constants")

Ponownie, nie jest to stała poprawka, ale może pomóc komuś, kto chce naprawić błąd importu bez zmiany zbyt dużej części kodu.

Twoje zdrowie!

radtek
źródło
3

Jest tu wiele świetnych odpowiedzi. Chociaż zwykle istnieją szybkie rozwiązania problemu, z których niektóre wydają się bardziej pytoniczne niż inne, jeśli masz luksus polegający na refaktoryzacji, innym podejściem jest analiza organizacji kodu i próba usunięcia zależności cyklicznej. Możesz na przykład stwierdzić, że masz:

Plik a.py

from b import B

class A:
    @staticmethod
    def save_result(result):
        print('save the result')

    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

Plik b.py

from a import A

class B:
    @staticmethod
    def do_something_b_ish(param):
        A.save_result(B.use_param_like_b_would(param))

W takim przypadku wystarczy przenieść jedną statyczną metodę do osobnego pliku, powiedz c.py:

Plik c.py

def save_result(result):
    print('save the result')

pozwoli na usunięcie save_resultmetody z A, a tym samym na usunięcie importu A z punktu b:

Plik refaktoryzowany a.py

from b import B
from c import save_result

class A:
    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

Plik refaktoryzowany b.py

from c import save_result

class B:
    @staticmethod
    def do_something_b_ish(param):
        save_result(B.use_param_like_b_would(param))

Podsumowując, jeśli masz narzędzie (np. Pylint lub PyCharm), które informuje o metodach, które mogą być statyczne, samo nałożenie staticmethodna nie dekoratora może nie być najlepszym sposobem na wyciszenie ostrzeżenia. Mimo że metoda wydaje się powiązana z klasą, może być lepiej ją oddzielić, zwłaszcza jeśli masz kilka blisko powiązanych modułów, które mogą wymagać tej samej funkcjonalności i zamierzasz ćwiczyć zasady DRY.

hlongmore
źródło
2

Okrągły import może być mylący, ponieważ import ma dwie rzeczy:

  1. wykonuje importowany kod modułu
  2. dodaje importowany moduł do importowanej globalnej tabeli symboli

Pierwsze jest wykonywane tylko raz, a drugie przy każdym wyciągu z importu. Okrągły import tworzy sytuację, gdy moduł importujący używa importowanego z częściowo wykonanym kodem. W rezultacie nie zobaczy obiektów utworzonych po instrukcji importu. Poniżej pokazano przykładowy kod.

Okrągły import nie jest ostatecznym złem, którego należy unikać za wszelką cenę. W niektórych frameworkach, takich jak Flask, są one dość naturalne, a modyfikacja kodu w celu ich wyeliminowania nie poprawia kodu.

main.py

print 'import b'
import b
print 'a in globals() {}'.format('a' in globals())
print 'import a'
import a
print 'a in globals() {}'.format('a' in globals())
if __name__ == '__main__':
    print 'imports done'
    print 'b has y {}, a is b.a {}'.format(hasattr(b, 'y'), a is b.a)

b.by

print "b in, __name__ = {}".format(__name__)
x = 3
print 'b imports a'
import a
y = 5
print "b out"

a.py

print 'a in, __name__ = {}'.format(__name__)
print 'a imports b'
import b
print 'b has x {}'.format(hasattr(b, 'x'))
print 'b has y {}'.format(hasattr(b, 'y'))
print "a out"

wyjście main.py python z komentarzami

import b
b in, __name__ = b    # b code execution started
b imports a
a in, __name__ = a    # a code execution started
a imports b           # b code execution is already in progress
b has x True
b has y False         # b defines y after a import,
a out
b out
a in globals() False  # import only adds a to main global symbol table 
import a
a in globals() True
imports done
b has y True, a is b.a True # all b objects are available
Jacek Błocki
źródło
1

Rozwiązałem problem w następujący sposób i działa dobrze bez żadnych błędów. Rozważ dwa pliki a.pyi b.py.

Dodałem to a.pyi zadziałało.

if __name__ == "__main__":
        main ()

a.py:

import b
y = 2
def main():
    print ("a out")
    print (b.x)

if __name__ == "__main__":
    main ()

b.py:

import a
print ("b out")
x = 3 + a.y

Otrzymałem wynik

>>> b out 
>>> a out 
>>> 5
Irfanuddin
źródło
0

Ok, myślę, że mam całkiem fajne rozwiązanie. Powiedzmy, że masz plik ai plik b. Masz deflub classw pliku b, który chcesz użyć w module a, ale trzeba coś innego, albo def, classlub zmienna z pliku a, który trzeba w swojej definicji lub klasy w pliku b. To, co możesz zrobić, to na dole pliku a, po wywołaniu funkcji lub klasy w pliku, aktóra jest potrzebna w pliku b, ale przed wywołaniem funkcji lub klasy z pliku b, które potrzebujesz do pliku a, powiedz import b wtedy, a oto kluczowa część , we wszystkich definicjach lub klasach w pliku, bktóre wymagają pliku deflub classz plikua(nazwijmy to CLASS), mówiszfrom a import CLASS

Działa to, ponieważ można zaimportować plik bbez wykonywania przez Pythona żadnej z instrukcji importu w pliku b, a tym samym uniknąć importu cyklicznego.

Na przykład:

Plik a:

class A(object):

     def __init__(self, name):

         self.name = name

CLASS = A("me")

import b

go = B(6)

go.dostuff

Plik b:

class B(object):

     def __init__(self, number):

         self.number = number

     def dostuff(self):

         from a import CLASS

         print "Hello " + CLASS.name + ", " + str(number) + " is an interesting number."

Voila

Cary Shindell
źródło
from a import CLASStak naprawdę nie pomija wykonywania całego kodu w a.py. Oto, co się naprawdę dzieje: (1) Cały kod w pliku a.py jest uruchamiany jako specjalny moduł „__main__”. (2) W import bmomencie uruchamiany jest kod najwyższego poziomu w b.py (definiowanie klasy B), a następnie kontrola powraca do „__main__”. (3) „__main__” ostatecznie przekazuje kontrolę go.dostuff(). (4) kiedy DoStuff () przychodzi import a, to działa cały kod w a.py ponownie , tym razem jako modułu „a”; następnie importuje obiekt CLASS z nowego modułu „a”. Tak naprawdę działałoby to równie dobrze, gdybyś używał import agdziekolwiek w b.py.
Matthias Fripp,