Python-Zirkelimport?

97

Ich bekomme also diesen Fehler

Traceback (most recent call last):
  File "/Users/alex/dev/runswift/utils/sim2014/simulator.py", line 3, in <module>
    from world import World
  File "/Users/alex/dev/runswift/utils/sim2014/world.py", line 2, in <module>
    from entities.field import Field
  File "/Users/alex/dev/runswift/utils/sim2014/entities/field.py", line 2, in <module>
    from entities.goal import Goal
  File "/Users/alex/dev/runswift/utils/sim2014/entities/goal.py", line 2, in <module>
    from entities.post import Post
  File "/Users/alex/dev/runswift/utils/sim2014/entities/post.py", line 4, in <module>
    from physics import PostBody
  File "/Users/alex/dev/runswift/utils/sim2014/physics.py", line 21, in <module>
    from entities.post import Post
ImportError: cannot import name Post

und Sie können sehen, dass ich die gleiche Importanweisung weiter oben verwende und es funktioniert? Gibt es eine ungeschriebene Regel für den zirkulären Import? Wie verwende ich dieselbe Klasse weiter unten im Aufrufstapel?

CpILL
quelle

Antworten:

161

Ich denke, die Antwort von jpmc26 ist zwar keineswegs falsch , aber bei zirkulären Importen zu stark. Sie können einwandfrei funktionieren, wenn Sie sie richtig eingerichtet haben.

Der einfachste Weg, dies zu tun, ist die Verwendung der import my_moduleSyntax anstelle von from my_module import some_object. Ersteres wird fast immer funktionieren, auch wenn my_modulees uns zurück importiert. Letzteres funktioniert nur, wenn my_objectes bereits in definiert ist my_module, was bei einem Zirkelimport möglicherweise nicht der Fall ist.

Um spezifisch für Ihren Fall zu sein: Versuchen Sie, entities/post.pyzu do zu wechseln , import physicsund beziehen Sie sich dann physics.PostBodynicht nur PostBodydirekt auf. In ähnlicher Weise ändern Sie physics.pyzu tun import entities.postund verwenden Sie dann entities.post.Postanstatt nur Post.

Blckknght
quelle
5
Ist diese Antwort mit relativen Importen kompatibel?
Joe
17
Warum passiert das?
Juan Pablo Santos
4
Es ist falsch zu sagen, dass Nicht- fromSyntax immer funktioniert. Wenn ich class A(object): pass; class C(b.B): passin Modul a und class B(a.A): passin Modul b habe, ist der zirkuläre Import immer noch ein Problem und dies wird nicht funktionieren.
CrazyCasta
1
Sie haben Recht, alle zirkulären Abhängigkeiten im Code der obersten Ebene der Module (wie die Basisklassen der Klassendeklarationen in Ihrem Beispiel) sind ein Problem. Dies ist die Situation, in der die Antwort von jpmc, dass Sie die Modulorganisation umgestalten sollten, wahrscheinlich zu 100% richtig ist. Verschieben Sie entweder die Klasse Bin das Modul aoder die Klasse Cin das Modul, bdamit Sie den Zyklus unterbrechen können. Es ist auch erwähnenswert, dass selbst wenn nur eine Richtung des Kreises Code der obersten Ebene enthält (z. B. wenn Ckeine Klasse vorhanden ist), möglicherweise eine Fehlermeldung angezeigt wird, je nachdem, welches Modul zuerst von einem anderen Code importiert wurde.
Blckknght
2
@ TylerCrompton: Ich bin mir nicht sicher, was Sie unter "Modulimport muss absolut sein" verstehen. Zirkuläre relative Importe können funktionieren, solange Sie Module importieren, nicht deren Inhalt (z . B. from . import sibling_modulenicht from .sibling_module import SomeClass). Es gibt etwas mehr Subtilität, wenn die __init__.pyDatei eines Pakets am zirkulären Import beteiligt ist, aber das Problem ist sowohl selten als auch wahrscheinlich ein Fehler in der importImplementierung. Siehe Python-Fehler 23447 , für den ich einen Patch eingereicht habe (der leider gelitten hat).
Blckknght
51

Wenn Sie ein Modul (oder ein Mitglied davon) zum ersten Mal importieren, wird der Code im Modul wie jeder andere Code nacheinander ausgeführt. zB wird es nicht anders behandelt als der Körper einer Funktion. Ein importnur ein Befehl wie jede andere (Zuordnung, ein Funktionsaufruf, def, class). Angenommen, Ihre Importe finden oben im Skript statt, dann passiert Folgendes:

  • Wenn Sie versuchen, Worldaus zu importieren world, wird das worldSkript ausgeführt.
  • Das worldSkript wird importiert Field, wodurch das entities.fieldSkript ausgeführt wird.
  • Dieser Vorgang wird fortgesetzt, bis Sie das entities.postSkript erreichen, weil Sie versucht haben, es zu importierenPost
  • Das entities.postSkript bewirkt, dass das physicsModul ausgeführt wird, weil es versucht zu importierenPostBody
  • Schließlich physicsversucht, Postaus zu importierenentities.post
  • Ich bin mir nicht sicher, ob das entities.postModul noch im Speicher vorhanden ist, aber es spielt wirklich keine Rolle. Entweder befindet sich das Modul nicht im Speicher, oder das Modul hat noch kein PostMitglied, da die Ausführung der Definition noch nicht abgeschlossen istPost
  • In beiden Fällen tritt ein Fehler auf, da er Postnicht importiert werden kann

Also nein, es ist nicht "weiter oben im Aufrufstapel". Dies ist eine Stapelverfolgung, in der der Fehler aufgetreten ist. Dies bedeutet, dass beim Import Postin diese Klasse ein Fehler aufgetreten ist . Sie sollten keine zirkulären Importe verwenden. Bestenfalls hat es einen vernachlässigbaren Nutzen (normalerweise keinen Nutzen) und verursacht solche Probleme. Es belastet jeden Entwickler, der es wartet, und zwingt ihn, auf Eierschalen zu laufen, um ein Zerbrechen zu vermeiden. Überarbeiten Sie Ihre Modulorganisation.

jpmc26
quelle
1
Sollte sein isinstance(userData, Post). Unabhängig davon haben Sie keine Wahl. Der zirkuläre Import funktioniert nicht. Die Tatsache, dass Sie zirkuläre Importe haben, ist für mich ein Codegeruch. Es wird vorgeschlagen, dass Sie einige Funktionen haben, die auf ein drittes Modul verschoben werden sollten. Ich konnte nicht sagen, was, ohne beide Klassen zu betrachten.
jpmc26
3
@CpILL Nach einer Weile kam mir eine sehr hackige Option in den Sinn. Wenn Sie dies nicht zu tun für jetzt (aus Zeitgründen oder , was Sie haben) umgehen kann, dann Sie könnte tun , um Ihre Import lokal innerhalb der Methode , wo Sie es verwenden. Ein Funktionskörper im Inneren defwird erst ausgeführt, wenn die Funktion aufgerufen wird. Der Import erfolgt also erst, wenn Sie die Funktion tatsächlich aufgerufen haben. Bis dahin sollte das imports funktionieren, da eines der Module vor dem Aufruf vollständig importiert worden wäre. Das ist ein absolut widerlicher Hack, und er sollte nicht für längere Zeit in Ihrer Codebasis verbleiben.
jpmc26
15
Ich denke, Ihre Antwort kommt bei zirkulären Importen zu schwer. Zirkuläre Importe funktionieren normalerweise, wenn Sie dies nur tun import foound nicht from foo import Bar. Das liegt daran, dass die meisten Module nur Dinge definieren (wie Funktionen und Klassen), die später ausgeführt werden. Module, die beim Importieren wichtige Aufgaben ausführen (z. B. ein Skript, das nicht durch geschützt ist if __name__ == "__main__"), sind möglicherweise immer noch problematisch, aber das ist nicht allzu häufig.
Blckknght
6
@Blckknght Ich denke, dass Sie sich darauf einstellen, Zeit mit seltsamen Problemen zu verbringen, die andere Leute untersuchen und verwirren müssen, wenn Sie zirkuläre Importe verwenden. Sie zwingen Sie dazu, Zeit damit zu verbringen, darauf zu achten, nicht über sie zu stolpern. Hinzu kommt ein Code-Geruch, den Ihr Design umgestalten muss. Ich habe mich vielleicht geirrt, ob sie technisch machbar sind, aber sie sind eine schreckliche Wahl für das Design, die früher oder später Probleme verursachen soll. Klarheit und Einfachheit sind heilige Grale in der Programmierung, und zirkuläre Importe verletzen beide in meinem Buch.
jpmc26
6
Alternative; Sie haben Ihre Funktionalität zu stark aufgeteilt, und das ist die Ursache für die zirkulären Importe. Wenn Sie zwei Dinge haben, die die ganze Zeit aufeinander angewiesen sind ; Es kann am besten sein, sie nur in einer Datei abzulegen. Python ist nicht Java; Kein Grund, Funktionen / Klassen nicht in einer einzigen Datei zu gruppieren, um eine seltsame Importlogik zu vermeiden. :-)
Mark Ribau
40

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.

Gene Olson
quelle
2
Ich glaube nicht, dass es in Python überhaupt eine Compiler- oder Kompilierungszeit gibt
pkqxdd
6
Python hat einen Compiler und ist @pkqxdd kompiliert. Die Kompilierung ist normalerweise nur dem Benutzer verborgen. Dies mag etwas verwirrend sein, aber es wäre für den Autor schwierig, diese bewundernswert klare Beschreibung der Vorgänge zu geben, ohne auf Pythons etwas verdeckte "Kompilierungszeit" Bezug zu nehmen.
Hank
Ich habe dies auf meiner Maschine versucht und ein anderes Ergebnis erzielt. Ran X.py hat aber den Fehler "Name 'Y2' kann nicht von 'Y' importiert werden" erhalten. Ran Y.py ohne Probleme. Ich bin auf Python 3.7.5. Können Sie mir helfen, das Problem hier zu erklären?
Xuefeng Huang
18

Für diejenigen unter Ihnen, die wie ich aus Django zu diesem Problem kommen, sollten Sie wissen, dass die Dokumente eine Lösung bieten: https://docs.djangoproject.com/de/1.10/ref/models/fields/#foreignkey

"... Um auf Modelle zu verweisen, die in einer anderen Anwendung definiert sind, können Sie explizit ein Modell mit der vollständigen Anwendungsbezeichnung angeben. Wenn beispielsweise das obige Herstellermodell in einer anderen Anwendung namens Produktion definiert ist, müssen Sie Folgendes verwenden:

class Car(models.Model):
    manufacturer = models.ForeignKey(
        'production.Manufacturer',
        on_delete=models.CASCADE,
)

Diese Art von Referenz kann nützlich sein, wenn zirkuläre Importabhängigkeiten zwischen zwei Anwendungen aufgelöst werden. ... "

Malik A. Rumi
quelle
6
Ich weiß, dass ich keinen Kommentar verwenden soll, um "Danke" zu sagen, aber das plagt mich seit ein paar Stunden. Danke danke danke!!!
MikeyE
Ich stimme @MikeyE zu. Ich habe mehrere Blogs und Stackoverflows gelesen, um dies mit PonyORM zu beheben. Wo andere sagen, dass es eine schlechte Praxis ist oder warum Sie Ihre Klassen so codieren, dass sie zirkulär sind, sind ORMs genau dort, wo dies geschieht. Da in vielen Beispielen alle Modelle in derselben Datei gespeichert sind und wir diesen Beispielen folgen, außer dass wir ein Modell pro Datei verwenden, ist das Problem nicht klar, wenn Python nicht kompiliert werden kann. Die Antwort ist jedoch so einfach. Wie Mike betonte, vielen Dank.
trash80
2

Wenn Sie in einer recht komplexen App auf dieses Problem stoßen, kann es umständlich sein, alle Ihre Importe zu überarbeiten. PyCharm bietet hierfür einen Quickfix an, der automatisch auch die Verwendung der importierten Symbole ändert.

Geben Sie hier die Bildbeschreibung ein

Andreas Bergström
quelle
2

Ich konnte das Modul innerhalb der Funktion (nur) importieren, für die die Objekte aus diesem Modul erforderlich sind:

def my_func():
    import Foo
    foo_instance = Foo()
Alexander Shubert
quelle
0

Ich habe folgendes verwendet:

from module import Foo

foo_instance = Foo()

aber um loszuwerden, habe circular referenceich folgendes getan und es hat funktioniert:

import module.foo

foo_instance = foo.Foo()
MKJ
quelle