Um zirkuläre Abhängigkeiten zu verstehen, müssen Sie sich daran erinnern, dass Python im Wesentlichen eine Skriptsprache ist. Die Ausführung von Anweisungen außerhalb von Methoden erfolgt zur Kompilierungszeit. Importanweisungen werden wie Methodenaufrufe ausgeführt. Um sie zu verstehen, sollten Sie sie wie Methodenaufrufe betrachten.
Wenn Sie einen Import durchführen, hängt das davon ab, ob die zu importierende Datei bereits in der Modultabelle vorhanden ist. In diesem Fall verwendet Python alles, was derzeit in der Symboltabelle enthalten ist. Wenn nicht, beginnt Python mit dem Lesen der Moduldatei und kompiliert / führt / importiert alles, was dort gefunden wird. Symbole, auf die beim Kompilieren verwiesen wird, werden gefunden oder nicht, je nachdem, ob sie vom Compiler gesehen wurden oder noch nicht gesehen wurden.
Stellen Sie sich vor, Sie haben zwei Quelldateien:
Datei X.py.
def X1:
return "x1"
from Y import Y2
def X2:
return "x2"
Datei Y.py.
def Y1:
return "y1"
from X import X1
def Y2:
return "y2"
Angenommen, Sie kompilieren die Datei X.py. Der Compiler definiert zunächst die Methode X1 und trifft dann die Importanweisung in X.py. Dies führt dazu, dass der Compiler die Kompilierung von X.py unterbricht und mit dem Kompilieren von Y.py beginnt. Kurz danach trifft der Compiler die Importanweisung in Y.py. Da X.py bereits in der Modultabelle enthalten ist, verwendet Python die vorhandene unvollständige X.py-Symboltabelle, um alle angeforderten Referenzen zu erfüllen. Alle Symbole, die vor der import-Anweisung in X.py erscheinen, befinden sich jetzt in der Symboltabelle, alle Symbole danach jedoch nicht. Da X1 jetzt vor der Importanweisung angezeigt wird, wurde es erfolgreich importiert. Python setzt dann das Kompilieren von Y.py fort. Dabei wird Y2 definiert und das Kompilieren von Y.py abgeschlossen. Anschließend wird die Kompilierung von X.py fortgesetzt und Y2 in der Y.py-Symboltabelle gefunden. Die Kompilierung wird schließlich ohne Fehler abgeschlossen.
Etwas ganz anderes passiert, wenn Sie versuchen, Y.py über die Befehlszeile zu kompilieren. Beim Kompilieren von Y.py trifft der Compiler die Importanweisung, bevor er Y2 definiert. Dann beginnt es mit dem Kompilieren von X.py. Bald trifft es die Import-Anweisung in X.py, die Y2 erfordert. Y2 ist jedoch undefiniert, sodass die Kompilierung fehlschlägt.
Beachten Sie, dass die Kompilierung immer erfolgreich ist, wenn Sie X.py so ändern, dass Y1 importiert wird, unabhängig davon, welche Datei Sie kompilieren. Wenn Sie jedoch die Datei Y.py so ändern, dass das Symbol X2 importiert wird, wird keine der Dateien kompiliert.
Verwenden Sie NICHT, wenn Modul X oder ein von X importiertes Modul das aktuelle Modul importiert:
from X import Y
Jedes Mal, wenn Sie glauben, dass es zu einem zirkulären Import kommen könnte, sollten Sie auch vermeiden, Verweise auf Variablen in anderen Modulen zu kompilieren. Betrachten Sie den unschuldig aussehenden Code:
import X
z = X.Y
Angenommen, Modul X importiert dieses Modul, bevor dieses Modul X importiert. Angenommen, Y wird nach der Importanweisung in X definiert. Dann wird Y nicht definiert, wenn dieses Modul importiert wird, und Sie erhalten einen Kompilierungsfehler. Wenn dieses Modul zuerst Y importiert, können Sie damit durchkommen. Wenn jedoch einer Ihrer Mitarbeiter die Reihenfolge der Definitionen in einem dritten Modul unschuldig ändert, wird der Code unterbrochen.
In einigen Fällen können Sie zirkuläre Abhängigkeiten auflösen, indem Sie eine Importanweisung unter die Symboldefinitionen verschieben, die von anderen Modulen benötigt werden. In den obigen Beispielen schlagen Definitionen vor der Importanweisung niemals fehl. Definitionen nach der import-Anweisung schlagen je nach Kompilierungsreihenfolge manchmal fehl. Sie können sogar Importanweisungen am Ende einer Datei einfügen, sofern beim Kompilieren keines der importierten Symbole benötigt wird.
Beachten Sie, dass das Verschieben von Importanweisungen in einem Modul Ihre Arbeit verdeckt. Kompensieren Sie dies mit einem Kommentar oben in Ihrem Modul wie folgt:
#import X (actual import moved down to avoid circular dependency)
Im Allgemeinen ist dies eine schlechte Praxis, aber manchmal ist es schwer zu vermeiden.