Zirkuläre (oder zyklische) Importe in Python


351

Was passiert, wenn sich zwei Module gegenseitig importieren?

Was ist mit den zyklischen Importen in Python, um das Problem zu verallgemeinern?



1
auch nur als Referenz scheint es, dass zirkuläre Importe auf Python 3.5 (und wahrscheinlich darüber hinaus) erlaubt sind, aber nicht auf 3.4 (und wahrscheinlich unten).
Charlie Parker

4
Ich verwende Python 3.7.2 und habe aufgrund zirkulärer Abhängigkeiten immer noch einen Laufzeitfehler.
Richard Whitehead

Antworten:


280

Letztes Jahr gab es auf comp.lang.python eine wirklich gute Diskussion darüber . Es beantwortet Ihre Frage ziemlich gründlich.

Importe sind wirklich ziemlich einfach. Denken Sie nur an Folgendes:

'import' und 'from xxx import yyy' sind ausführbare Anweisungen. Sie werden ausgeführt, wenn das laufende Programm diese Zeile erreicht.

Befindet sich ein Modul nicht in sys.modules, erstellt ein Import den neuen Moduleintrag in sys.modules und führt dann den Code im Modul aus. Die Steuerung wird erst nach Abschluss der Ausführung an das aufrufende Modul zurückgegeben.

Wenn ein Modul in sys.modules vorhanden ist, gibt ein Import dieses Modul einfach zurück, unabhängig davon, ob die Ausführung abgeschlossen ist oder nicht. Aus diesem Grund können zyklische Importe Module zurückgeben, die teilweise leer zu sein scheinen.

Schließlich wird das ausführende Skript in einem Modul mit dem Namen __main__ ausgeführt. Wenn Sie das Skript unter seinem eigenen Namen importieren, wird ein neues Modul erstellt, das nicht mit __main__ zusammenhängt.

Nehmen Sie dieses Los zusammen und Sie sollten beim Importieren von Modulen keine Überraschungen erleben.


13
@meawoppl Könnten Sie diesen Kommentar bitte erweitern? Wie konkret haben sie sich verändert?
Dan Schien

3
Ab sofort der einzige Verweis auf zirkuläre Importe in Python3 "Was ist neu?" Seiten ist in der 3.5 . Es heißt "Zirkuläre Importe mit relativen Importen werden jetzt unterstützt". @meawoppl Haben Sie noch etwas gefunden, was auf diesen Seiten nicht aufgeführt ist?
Zezollo

4
Sie sind def. wird in 3.0-3.4 nicht unterstützt. Oder zumindest die Semantik für den Erfolg ist anders. Hier ist eine Zusammenfassung, die ich gefunden habe und die die 3.5-Änderungen nicht erwähnt. gist.github.com/datagrok/40bf84d5870c41a77dc6
meawoppl

Können Sie dies erweitern? "Schließlich wird das ausführende Skript in einem Modul namens main ausgeführt . Wenn Sie das Skript unter seinem eigenen Namen importieren, wird ein neues Modul erstellt, das nichts mit main zu tun hat ." Nehmen wir also an, die Datei ist a.py und wenn sie als Haupteinstiegspunkt ausgeführt wird, ist sie jetzt die Hauptdatei, wenn sie Code wie bei einem Import einer Variablen enthält. Wird dann dieselbe Datei 'a.py' in die Tabelle der sys-Module geladen? Bedeutet das also, dass es zweimal ausgeführt wird, wenn es eine print-Anweisung hat? Einmal für die Hauptdatei und noch einmal beim Import?
Variable

Diese Antwort ist 10 Jahre alt und ich möchte ein modernisiertes Update, um sicherzustellen, dass es in verschiedenen Versionen von Python, 2.x oder 3.x
Fallenreaper

296

Wenn Sie import fooinnen barund import barinnen tun foo, wird es gut funktionieren. Wenn tatsächlich etwas ausgeführt wird, sind beide Module vollständig geladen und haben Verweise aufeinander.

Das Problem ist, wenn Sie stattdessen from foo import abcund from bar import xyz. Denn jetzt muss für jedes Modul das andere Modul bereits importiert sein (damit der zu importierende Name vorhanden ist), bevor es importiert werden kann.


27
Es scheint das from foo import *und from bar import *wird auch gut funktionieren.
Akavall

1
Überprüfen Sie die Bearbeitung des obigen Beitrags mit a.py/b.py. Er verwendet nicht from x import yund erhält dennoch den zirkulären Importfehler
Greg Ennis

2
Dies ist nicht ganz richtig. Wenn Sie wie beim Importieren von * auf der obersten Ebene versuchen, auf ein Element im zirkulären Import zuzugreifen, tritt das gleiche Problem auf, bevor das Skript ausgeführt wird. Zum Beispiel, wenn Sie ein Paket global in einem Paket von einem anderen festlegen und beide sich gegenseitig einschließen. Ich habe dies getan, um eine schlampige Factory für ein Objekt in der Basisklasse zu erstellen, in der dieses Objekt eine von mehreren Unterklassen sein kann und der Verwendungscode nicht wissen muss, welches Objekt tatsächlich erstellt wird.
AaronM

3
@ Akavall Nicht wirklich. Dadurch werden nur die Namen importiert, die verfügbar sind, wenn die importAnweisung ausgeführt wird. Es wird also kein Fehler auftreten, aber Sie erhalten möglicherweise nicht alle Variablen, die Sie erwarten.
August

3
Beachten Sie, wenn Sie from foo import *und tun from bar import *, befindet sich alles, was in der ausgeführt foowird, in der Initialisierungsphase von barund die tatsächlichen Funktionen in wurden barnoch nicht definiert ...
Martian2049

100

Zyklische Importe werden beendet, Sie müssen jedoch darauf achten, die zyklisch importierten Module während der Modulinitialisierung nicht zu verwenden.

Betrachten Sie die folgenden Dateien:

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

Wenn Sie a.py ausführen, erhalten Sie Folgendes:

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

Beim zweiten Import von b.py (im zweiten a in) wird der Python-Interpreter nicht berneut importiert , da er bereits im Modul dict vorhanden ist.

Wenn Sie versuchen, zuzugreifen b.xaus awährend Modulinitialisierung, erhalten Sie ein AttributeError.

Fügen Sie die folgende Zeile hinzu a.py:

print b.x

Dann ist die Ausgabe:

$ 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'

Dies liegt daran, dass Module beim Import ausgeführt werden und zum Zeitpunkt des b.xZugriffs die Zeile x = 3noch nicht ausgeführt wurde, was erst danach geschieht b out.


14
Dies erklärt das Problem sehr, aber wie steht es mit der Lösung? Wie können wir x richtig importieren und drucken? Die andere Lösung oben hat bei mir nicht funktioniert
Mehmet

Ich denke, diese Antwort würde viel nützen, wenn Sie __name__stattdessen verwenden würden 'a'. Am Anfang war ich total verwirrt, warum eine Datei zweimal ausgeführt wird.
Bergi

30

Wie andere Antworten beschreiben, ist dieses Muster in Python akzeptabel:

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

Dadurch wird die Ausführung der Importanweisung vermieden, wenn die Datei von anderen Modulen importiert wird. Nur wenn eine logische zirkuläre Abhängigkeit besteht, schlägt dies fehl.

Die meisten Zirkularimporte sind keine logischen Zirkularimporte, sondern verursachen ImportErrorFehler, da import()beim Aufruf Anweisungen der obersten Ebene der gesamten Datei ausgewertet werden.

Diese ImportErrorskönnen fast immer vermieden werden, wenn Sie Ihre Importe ganz oben haben möchten :

Betrachten Sie diesen zirkulären Import:

App A.

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

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

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

App B.

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

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

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

Von David Beazleys exzellenten Vortragsmodulen und -paketen: Live and Let Die! - PyCon 2015 , 1:54:00hier ist eine Möglichkeit, mit zirkulären Importen in Python umzugehen:

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

Dies versucht zu importieren SimplifiedImageSerializerund wenn ImportErrores ausgelöst wird, weil es bereits importiert ist, wird es aus dem Importcache gezogen.

PS: Sie müssen diesen gesamten Beitrag in David Beazleys Stimme lesen.


9
ImportError wird nicht ausgelöst, wenn das Modul bereits importiert wurde. Module können so oft importiert werden, wie Sie möchten, dh "import a; import a;" ist in Ordnung.
Yuras

9

Ich habe hier ein Beispiel, das mich beeindruckt hat!

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"

In der Befehlszeile: $ 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

2
Wie haben Sie das behoben? Ich versuche, den zirkulären Import zu verstehen, um ein eigenes Problem zu beheben, das dem, was Sie tun , sehr ähnlich sieht ...
c089

12
Ähm ... Ich glaube, ich habe mein Problem mit diesem unglaublich hässlichen Hack behoben. {{{wenn nicht 'foo.bar' in sys.modules: von foo import bar sonst: bar = sys.modules ['foo.bar']}}} Persönlich denke ich, dass zirkuläre Importe ein RIESIGES Warnzeichen für schlechten Code sind Design ...
c089

5
@ C089, oder Sie könnten nur bewegen import barin foo.pybis zum Ende
warvariuc

5
Wenn barund foobeide verwenden müssen gX, besteht die "sauberste" Lösung darin, gXein anderes Modul einzubauen und beide zu haben foound bardieses Modul zu importieren. (Am saubersten in dem Sinne, dass es keine versteckten semantischen Abhängigkeiten gibt.)
Tim Wilder

2
Tim hat einen guten Punkt. Im Grunde ist es, weil barich nicht einmal gXim Foo finden kann. Der zirkuläre Import ist an sich in Ordnung, aber es ist nur so, dass gXer beim Import nicht definiert wird.
Martian2049

9

Modul a.py:

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

Modul b.py.

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

Wenn Sie "Modul a" ausführen, wird Folgendes ausgegeben:

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

Es gab diese 3 Zeilen aus, während es wegen des zirkulären Imports Infinitiv ausgeben sollte. Was beim Ausführen von "Modul a" Zeile für Zeile passiert, ist hier aufgeführt:

  1. Die erste Zeile ist import b. so wird es Modul b besuchen
  2. Die erste Zeile in Modul b ist import a. so wird es Modul a besuchen
  3. Die erste Zeile in Modul a ist, import baber beachten Sie, dass diese Zeile nicht mehr erneut ausgeführt wird , da jede Datei in Python eine Importzeile nur einmal ausführt. Es spielt keine Rolle, wo oder wann sie ausgeführt wird. so wird es zur nächsten Zeile übergehen und drucken "This is from module a".
  4. Nach dem Besuch des gesamten Moduls a von Modul b befinden wir uns noch in Modul b. Die nächste Zeile wird also gedruckt"This is from module b"
  5. Die Zeilen von Modul b werden vollständig ausgeführt. Also kehren wir zu Modul a zurück, wo wir Modul b gestartet haben.
  6. import b line wurde bereits ausgeführt und wird nicht erneut ausgeführt. Die nächste Zeile wird gedruckt "This is from module a"und das Programm wird beendet.

4

Ich stimme der Antwort von Python hier voll und ganz zu. Ich bin jedoch auf Code gestoßen, der bei zirkulären Importen fehlerhaft war und Probleme beim Hinzufügen von Komponententests verursachte. Um es schnell zu patchen, ohne alles zu ändern, können Sie das Problem durch einen dynamischen Import beheben.

# 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")

Auch dies ist keine dauerhafte Korrektur, kann aber jemandem helfen, der einen Importfehler beheben möchte, ohne zu viel Code zu ändern.

Prost!


3

Hier gibt es viele gute Antworten. Während es normalerweise schnelle Lösungen für das Problem gibt, von denen sich einige pythonischer anfühlen als andere, besteht ein anderer Ansatz darin, die Organisation Ihres Codes zu analysieren und zu versuchen, die zirkuläre Abhängigkeit zu beseitigen, wenn Sie den Luxus haben, Refactoring durchzuführen. Sie können zum Beispiel feststellen, dass Sie:

Datei 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)

Datei 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))

In diesem Fall wird nur eine statische Methode in einer separaten Datei zu bewegen, sagen c.py:

Datei c.py.

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

ermöglicht das Entfernen der save_resultMethode aus A und damit das Entfernen des Imports von A aus a in b:

Refactored File 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)

Refactored File 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))

Wenn Sie ein Tool (z. B. Pylint oder PyCharm) haben, das über statische Methoden berichtet, ist es staticmethodmöglicherweise nicht die beste Möglichkeit, die Warnung auszuschalten, wenn Sie nur einen Dekorator darauf werfen . Obwohl die Methode mit der Klasse verwandt zu sein scheint, ist es möglicherweise besser, sie zu trennen, insbesondere wenn Sie mehrere eng verwandte Module haben, die möglicherweise dieselbe Funktionalität benötigen, und Sie beabsichtigen, DRY-Prinzipien zu üben.


2

Zirkuläre Importe können verwirrend sein, da der Import zwei Dinge bewirkt:

  1. Es führt importierten Modulcode aus
  2. Fügt ein importiertes Modul zur globalen Symboltabelle des Importmoduls hinzu

Ersteres wird nur einmal durchgeführt, letzteres bei jeder Importanweisung. Der zirkuläre Import schafft eine Situation, wenn beim Importieren eines Moduls ein importiertes Modul mit teilweise ausgeführtem Code verwendet wird. Infolgedessen werden keine Objekte angezeigt, die nach der Importanweisung erstellt wurden. Das folgende Codebeispiel zeigt dies.

Zirkuläre Importe sind nicht das ultimative Übel, das um jeden Preis vermieden werden muss. In einigen Frameworks wie Flask sind sie ganz natürlich, und wenn Sie Ihren Code optimieren, um sie zu entfernen, wird der Code nicht besser.

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"

python main.py Ausgabe mit Kommentaren

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

1

Ich habe das Problem folgendermaßen gelöst und es funktioniert ohne Fehler. Betrachten Sie zwei Dateien a.pyund b.py.

Ich habe das hinzugefügt a.pyund es hat funktioniert.

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

Die Ausgabe, die ich bekomme, ist

>>> b out 
>>> a out 
>>> 5

0

Ok, ich denke ich habe eine ziemlich coole Lösung. Angenommen, Sie haben Datei aund Datei b. Sie haben eine defoder eine classin der Datei , bdie Sie in Modul verwenden möchten a, aber Sie haben etwas anderes, entweder ein def, classoder eine Variable aus der Datei , adie Sie in Ihrer Definition oder Klasse in der Datei benötigen b. Was Sie tun können, ist am Ende der Datei a, nachdem Sie die Funktion oder Klasse in der Datei aufgerufen haben a, die in der Datei benötigt wird b, aber bevor Sie die Funktion oder Klasse aus der Datei aufrufen, die bSie für die Datei benötigen a, sagen Sie import b Dann, und hier ist der Schlüsselteil , in allen Definitionen oder Klassen in der Datei b, die das defoder benötigenclass Datei vona(nennen wir es CLASS), sagst dufrom a import CLASS

Dies funktioniert, weil Sie Dateien importieren können, bohne dass Python eine der Importanweisungen in der Datei ausführt b, und sich somit jeglichen zirkulären Importen entziehen.

Zum Beispiel:

Datei A:

class A(object):

     def __init__(self, name):

         self.name = name

CLASS = A("me")

import b

go = B(6)

go.dostuff

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


from a import CLASSüberspringt nicht die Ausführung des gesamten Codes in a.py. Dies ist, was wirklich passiert: (1) Der gesamte Code in a.py wird als spezielles Modul "__main__" ausgeführt. (2) Um import bwird der Code der obersten Ebene in b.py ausgeführt (Definition der Klasse B) und die Steuerung kehrt zu "__main__" zurück. (3) "__main__" übergibt schließlich die Kontrolle an go.dostuff(). (4) Wenn dostuff () zu kommt import a, läuft es den gesamten Code in a.py erneut , diesmal als das Modul „a“; dann importiert es das CLASS-Objekt aus dem neuen Modul "a". Eigentlich würde dies genauso gut funktionieren, wenn Sie import airgendwo in b.py verwenden würden.
Matthias Fripp
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.