Was passiert bei gegenseitigen oder zirkulären (zyklischen) Importen?

Lesezeit: 13 Minuten

Benutzeravatar von Xolve
Xolve

Was passiert in Python, wenn zwei Module dies versuchen? import gegenseitig? Allgemeiner gesagt, was passiert, wenn mehrere Module dies versuchen import im Kreislauf?


Siehe auch Was kann ich bei “ImportError: Cannot import name X” oder “AttributeError: … (höchstwahrscheinlich aufgrund eines zirkulären Imports)” tun? für das allgemeine Problem, das sich daraus ergeben kann, und Ratschläge zum Umschreiben von Code, um solche Importe zu vermeiden. Siehe Warum funktionieren kreisförmige Importe scheinbar weiter oben in der Aufrufliste, lösen dann aber einen ImportError weiter unten aus? für technische Details auf warum und wie das Problem tritt auf.

  • Siehe auch stackoverflow.com/questions/158268/…

    – Konstantin

    14. April 2009 um 14:58 Uhr

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

    – Charly Parker

    8. Februar 2017 um 15:17 Uhr

  • Ich verwende Python 3.7.2 und habe aufgrund von zirkulären Abhängigkeiten immer noch einen Laufzeitfehler.

    – Richard Weißkopf

    15. März 2019 um 10:19 Uhr

  • @CharlieParker Dies gilt insbesondere für relative Importe gemäß Was ist neu in 3.5. Der relevante Issue-Tracker-Eintrag lautet hier. Änderungen wurden auch in 3.7 vorgenommen zu unterstützen einige absolute Importfälle. Dies hindert jedoch nicht AttributeErrors – ermöglicht das Nachschlagen des teilweise initialisierten Moduls in sys.moduleslöst aber keine Zeitparadoxe auf.

    – Karl Knechtel

    11. August um 4:00 Uhr

Wenn Sie tun import foo (Innerhalb bar.py) und import bar (Innerhalb foo.py), es wird gut funktionieren. Bis etwas tatsächlich läuft, sind beide Module vollständig geladen und haben Verweise aufeinander.

Das Problem ist, wenn Sie es stattdessen tun from foo import abc (Innerhalb bar.py) und from bar import xyz (Innerhalb foo.py). Denn jetzt erfordert jedes Modul, dass das andere Modul bereits importiert ist (damit der Name, den wir importieren, existiert), bevor es importiert werden kann.

  • Es scheint, dass from foo import * und from bar import * wird auch gut funktionieren.

    – Akavall

    12. Mai 2014 um 16:54 Uhr


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

    – Greg Ennis

    30. Juni 2014 um 14:09 Uhr

  • Dies ist nicht ganz richtig. Genau wie import * from, wenn Sie versuchen, auf ein Element im kreisförmigen Import zuzugreifen, auf der obersten Ebene, also bevor das Skript seine Ausführung abschließt, haben Sie das gleiche Problem. 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 Fabrik für ein Objekt in der Basisklasse zu erstellen, bei der dieses Objekt eine von mehreren Unterklassen sein könnte und der verwendende Code nicht wissen musste, welche es tatsächlich erstellt.

    – AaronM

    13. April 2016 um 20:54 Uhr

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

    – Augurar

    24. Dezember 2016 um 1:41 Uhr

  • Beachten Sie, wenn Sie dies tun from foo import * und from bar import *alles in der ausgeführt foo befindet sich in der Initialisierungsphase von barund die tatsächlichen Funktionen in bar ist noch nicht definiert…

    – MarsianerMarsmensch

    18. Januar 2017 um 2:37 Uhr


Benutzeravatar von Shane C. Mason
Shane C. Mason

Es gab eine wirklich gute Diskussion darüber drüben bei comp.lang.python vergangenes Jahr. Es beantwortet Ihre Frage ziemlich ausführlich.

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.

Wenn sich ein Modul nicht in sys.modules befindet, erstellt ein Import den neuen Moduleintrag in sys.modules und führt dann den Code im Modul aus. Es gibt die Steuerung nicht an das aufrufende Modul zurück, bis die Ausführung abgeschlossen ist.

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

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 nichts mit __main__ zu tun hat.

Nehmen Sie diese Menge zusammen und Sie sollten keine Überraschungen beim Importieren von Modulen erleben.

  • @meawoppl Könntest du diesen Kommentar bitte erweitern? Wie konkret haben sie sich verändert?

    – Dan Schien

    7. April 2016 um 10:17 Uhr

  • Ab sofort ist der einzige Hinweis auf zirkuläre Importe in python3 “Was ist neu?” Seiten ist im 3,5er. Dort heißt es: “Zirkuläre Importe mit relativen Importen werden jetzt unterstützt”. @meawoppl hast du noch etwas gefunden, was nicht auf diesen Seiten aufgeführt ist?

    – Zezollo

    21. April 2016 um 5:38 Uhr


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

    – Meawoppl

    22. April 2016 um 19:02 Uhr

  • Können Sie dies bitte erweitern “Schließlich wird das ausführende Skript in einem Modul mit dem Namen ausgeführt hauptsächlichwird beim Importieren des Skripts unter seinem eigenen Namen ein neues Modul erstellt, das nichts damit zu tun hat hauptsächlich.”. Nehmen wir also an, die Datei ist a.py und wenn sie als Haupteinstiegspunkt ausgeführt wird, ist itsbthe hauptsächlich jetzt, wenn es Code wie von einem Import einiger Variablen hat. Wird dann dieselbe Datei ‘a.py’ in die sys-Modultabelle geladen? Bedeutet es also, dass es zweimal ausgeführt wird, wenn es eine print-Anweisung hat? Einmal für die Hauptdatei und noch einmal beim Importieren?

    – variabel

    30. August 2019 um 3:39 Uhr

  • Diese Antwort ist 10 Jahre alt, und ich hätte gerne ein modernisiertes Update, um sicherzustellen, dass sie in verschiedenen Versionen von Python, 2.x oder 3.x, korrekt bleibt

    – Fallenreaper

    30. Oktober 2019 um 20:15 Uhr

Benutzeravatar von Torsten Marek
Torsten Mark

Zyklische Importe werden beendet, aber Sie müssen darauf achten, die zyklisch importierten Module nicht während der Modulinitialisierung 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 (in der zweiten a in), importiert der Python-Interpreter nicht b wieder, weil es bereits im Modul dict existiert.

Wenn Sie versuchen, darauf zuzugreifen b.x aus a während der Modulinitialisierung erhalten Sie eine AttributeError.

Hängen Sie die folgende Zeile an an 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 und gleichzeitig ausgeführt werden b.x zugegriffen wird, die Zeile x = 3 noch nicht ausgeführt wurde, was erst danach geschehen wird b out.

  • Dies erklärt das Problem sehr gut, aber wie sieht es mit der Lösung aus? Wie könnten wir x richtig importieren und drucken? Die andere Lösung oben hat bei mir nicht funktioniert

    – Mehmet

    27. März 2018 um 18:23 Uhr

  • Ich denke, diese Antwort würde sehr von Nutzen sein, wenn Sie sie verwenden würden __name__ Anstatt von 'a'. Am Anfang war ich total verwirrt, warum eine Datei zweimal ausgeführt wird.

    – Bergi

    18. Februar 2020 um 2:46 Uhr

  • @mehmet Gestalten Sie Ihr Projekt so um, dass die Importanweisungen eine baumartige Struktur bilden (das Hauptskript importiert unterstützende Module, die selbst ihre unterstützenden Module usw. importieren können). Dies ist die allgemein empfehlenswerte Vorgehensweise.

    – Jeyekomon

    6. Januar 2021 um 11:30 Uhr

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

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

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

Die meisten Circular Imports sind eigentlich keine logischen Circular Imports, sondern erhöhen sie ImportError Fehler, wegen der Art und Weise import() wertet beim Aufruf Top-Level-Anweisungen der gesamten Datei aus.

Diese ImportErrors lässt sich fast immer vermeiden, wenn Sie Ihre Importe unbedingt oben drauf haben wollen:

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)

Anwendung B

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

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

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

Von David Beazleys ausgezeichnetem Vortrag Module und Pakete: Leben und sterben lassen! -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 SimplifiedImageSerializer und wenn ImportError ausgelöst wird, da es bereits importiert ist, wird es aus dem Importcache gezogen.

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

Benutzeravatar von Iterniam
Iterniam

Zu meiner Überraschung hat noch niemand zyklische Importe erwähnt, die durch Typhinweise verursacht wurden.
Wenn Sie zyklische Importe haben nur durch Type Hinting lassen sie sich sauber vermeiden.

In Betracht ziehen main.py die Ausnahmen aus einer anderen Datei verwendet:

from src.exceptions import SpecificException

class Foo:
    def __init__(self, attrib: int):
        self.attrib = attrib

raise SpecificException(Foo(5))

Und die dedizierte Ausnahmeklasse exceptions.py:

from src.main import Foo

class SpecificException(Exception):
    def __init__(self, cause: Foo):
        self.cause = cause

    def __str__(self):
        return f'Expected 3 but got {self.cause.attrib}.'

Dadurch wird ein erhöht ImportError seit main.py Importe exception.py und umgekehrt durch Foo und SpecificException.

Da Foo wird nur benötigt in exceptions.py Bei der Typprüfung können wir den Import sicher mit der Bedingung machen TYPE_CHECKING konstant von der tippen Modul. Die Konstante ist nur True bei der Typprüfung, die uns einen bedingten Import ermöglicht Foo und vermeiden Sie dadurch den zirkulären Importfehler.
Zu beachten ist, dass dabei Foo wird zur Laufzeit nicht in exceptions.py deklariert, was zu a führt NameError. Um das zu vermeiden, fügen wir hinzu from __future__ import annotations wodurch alle Typanmerkungen im Modul in Zeichenfolgen umgewandelt werden.

Daher erhalten wir den folgenden Code für Python 3.7+:

from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:  # Only imports the below statements during type checking
   ​from src.main import Foo

class SpecificException(Exception):
   def __init__(self, cause: Foo):  # Foo becomes 'Foo' because of the future import
       ​self.cause = cause

   ​def __str__(self):
       ​return f'Expected 3 but got {self.cause.attrib}.'

In Python 3.6 existiert der Future-Import also nicht Foo muss ein String sein:

from typing import TYPE_CHECKING
if TYPE_CHECKING:  # Only imports the below statements during type checking
   ​from src.main import Foo

class SpecificException(Exception):
   ​def __init__(self, cause: 'Foo'):  # Foo has to be a string
       ​self.cause = cause

   ​def __str__(self):
       ​return f'Expected 3 but got {self.cause.attrib}.'

In Python 3.5 und darunter war die Type-Hinting-Funktionalität noch nicht vorhanden.
In zukünftigen Versionen von Python wird die Anmerkungsfunktion möglicherweise obligatorisch, danach ist der zukünftige Import nicht mehr erforderlich. In welcher Version dies vorkommen wird, steht noch nicht fest.

Diese Antwort basiert auf Noch eine weitere Lösung, um Sie aus einem kreisförmigen Importloch in Python auszugraben von Stefan Lippens.

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 aufgrund des zirkulären Imports unendlich 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. es wird also Modul b besuchen
  2. Die erste Zeile bei Modul b ist import a. Es wird also Modul a besucht
  3. Die erste Zeile bei Modul a ist import b aber Beachten Sie, dass diese Zeile nicht mehr ausgeführt wird, da jede Datei in Python nur einmal eine Importzeile ausführt, spielt es keine Rolle, wo oder wann sie ausgeführt wird. es wird also zur nächsten Zeile übergehen und gedruckt "This is from module a".
  4. Nachdem wir das gesamte Modul a von Modul b besucht haben, sind wir immer noch bei Modul b. so wird die nächste Zeile gedruckt "This is from module b"
  5. Die Zeilen des Moduls b werden vollständig ausgeführt. Also gehen wir zurück zu Modul a, wo wir mit Modul b begonnen haben.
  6. import b line wurden bereits ausgeführt und werden nicht noch einmal ausgeführt. Die nächste Zeile wird gedruckt "This is from module a" und Programm wird beendet.

Mohs Benutzeravatar
Moh

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"

Auf der Kommandozeile: $ 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

  • Wie haben Sie das behoben? Ich versuche, den zirkulären Import zu verstehen, um ein eigenes Problem zu beheben, das aussieht sehr ähnlich wie du es machst…

    – c089

    9. August 2010 um 6:53 Uhr

  • Errm … Ich glaube, ich habe mein Problem mit diesem unglaublich hässlichen Hack behoben. {{{ falls nicht ‘foo.bar’ in sys.modules: from foo import bar sonst: bar = sys.modules[‘foo.bar’] }}} Persönlich denke ich, dass zirkuläre Importe ein RIESIGES Warnzeichen für schlechtes Code-Design sind …

    – c089

    9. August 2010 um 7:01 Uhr

  • @ c089, oder Sie könnten einfach umziehen import bar in foo.py bis zum Ende

    – warvariuc

    5. August 2013 um 9:52 Uhr

  • Wenn bar und foo beide müssen verwendet werden gXist die “sauberste” Lösung zu setzen gX in einem anderen Modul und haben beides foo und bar Importieren Sie dieses Modul. (am saubersten in dem Sinne, dass es keine versteckten semantischen Abhängigkeiten gibt.)

    – Tim Wilder

    17. Dezember 2013 um 20:32 Uhr


  • Tim hat einen guten Punkt. Im Grunde liegt es daran bar kann gar nicht finden gX im foo. Der zirkuläre Import ist an sich in Ordnung, aber es ist nur das gX ist beim Importieren nicht definiert.

    – MarsianerMarsmensch

    18. Januar 2017 um 3:31 Uhr

1436520cookie-checkWas passiert bei gegenseitigen oder zirkulären (zyklischen) Importen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy