Was passiert, wenn sich zwei Module gegenseitig importieren?
Was ist mit den zyklischen Importen in Python, um das Problem zu verallgemeinern?
Was passiert, wenn sich zwei Module gegenseitig importieren?
Was ist mit den zyklischen Importen in Python, um das Problem zu verallgemeinern?
Antworten:
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.
Wenn Sie import foo
innen bar
und import bar
innen 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 abc
und 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.
from foo import *
und from bar import *
wird auch gut funktionieren.
from x import y
und erhält dennoch den zirkulären Importfehler
import
Anweisung ausgeführt wird. Es wird also kein Fehler auftreten, aber Sie erhalten möglicherweise nicht alle Variablen, die Sie erwarten.
from foo import *
und tun from bar import *
, befindet sich alles, was in der ausgeführt foo
wird, in der Initialisierungsphase von bar
und die tatsächlichen Funktionen in wurden bar
noch nicht definiert ...
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 b
erneut importiert , da er bereits im Modul dict vorhanden ist.
Wenn Sie versuchen, zuzugreifen b.x
aus a
wä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.x
Zugriffs die Zeile x = 3
noch nicht ausgeführt wurde, was erst danach geschieht b out
.
__name__
stattdessen verwenden würden 'a'
. Am Anfang war ich total verwirrt, warum eine Datei zweimal ausgeführt wird.
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 ImportError
Fehler, da import()
beim Aufruf Anweisungen der obersten Ebene der gesamten Datei ausgewertet werden.
Diese ImportErrors
können fast immer vermieden werden, wenn Sie Ihre Importe ganz oben haben möchten :
Betrachten Sie diesen zirkulären Import:
# profiles/serializers.py
from images.serializers import SimplifiedImageSerializer
class SimplifiedProfileSerializer(serializers.Serializer):
name = serializers.CharField()
class ProfileSerializer(SimplifiedProfileSerializer):
recent_images = SimplifiedImageSerializer(many=True)
# 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:00
hier 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 SimplifiedImageSerializer
und wenn ImportError
es 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.
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
import bar
in foo.py
bis zum Ende
bar
und foo
beide verwenden müssen gX
, besteht die "sauberste" Lösung darin, gX
ein anderes Modul einzubauen und beide zu haben foo
und bar
dieses Modul zu importieren. (Am saubersten in dem Sinne, dass es keine versteckten semantischen Abhängigkeiten gibt.)
bar
ich nicht einmal gX
im Foo finden kann. Der zirkuläre Import ist an sich in Ordnung, aber es ist nur so, dass gX
er beim Import nicht definiert wird.
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:
import b
. so wird es Modul b besuchenimport a
. so wird es Modul a besuchenimport b
aber 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"
."This is from module b"
"This is from module a"
und das Programm wird beendet.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!
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_result
Methode 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 staticmethod
mö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.
Zirkuläre Importe können verwirrend sein, da der Import zwei Dinge bewirkt:
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
Ich habe das Problem folgendermaßen gelöst und es funktioniert ohne Fehler. Betrachten Sie zwei Dateien a.py
und b.py
.
Ich habe das hinzugefügt a.py
und es hat funktioniert.
if __name__ == "__main__":
main ()
import b
y = 2
def main():
print ("a out")
print (b.x)
if __name__ == "__main__":
main ()
import a
print ("b out")
x = 3 + a.y
Die Ausgabe, die ich bekomme, ist
>>> b out
>>> a out
>>> 5
Ok, ich denke ich habe eine ziemlich coole Lösung. Angenommen, Sie haben Datei a
und Datei b
. Sie haben eine def
oder eine class
in der Datei , b
die Sie in Modul verwenden möchten a
, aber Sie haben etwas anderes, entweder ein def
, class
oder eine Variable aus der Datei , a
die 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 b
Sie 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 def
oder benötigenclass
Datei vona
(nennen wir es CLASS
), sagst dufrom a import CLASS
Dies funktioniert, weil Sie Dateien importieren können, b
ohne dass Python eine der Importanweisungen in der Datei ausführt b
, und sich somit jeglichen zirkulären Importen entziehen.
Zum Beispiel:
class A(object):
def __init__(self, name):
self.name = name
CLASS = A("me")
import b
go = B(6)
go.dostuff
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 b
wird 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 a
irgendwo in b.py verwenden würden.