user.py:
%Vor%story.py
%Vor% Wie Sie sehen können, gibt es in diesem Programm einen zirkulären Import, der ein ImportError
verursacht. Ich habe gelernt, dass ich die Import-Anweisung in der Methodendefinition verschieben kann, um diesen Fehler zu vermeiden. Aber ich möchte immer noch wissen, gibt es eine Möglichkeit, in diesem Fall Kreisimport zu entfernen, oder, ist es notwendig (für ein gutes Design)?
Eine andere Möglichkeit, die Kreisförmigkeit zu verringern, besteht darin, den Importstil zu ändern. Ändern Sie from story import Story
in import story
und beziehen Sie sich dann auf die Klasse als story.Story
. Da Sie nur innerhalb einer Methode auf die Klasse verweisen, müssen Sie erst auf die Klasse zugreifen, wenn die Methode aufgerufen wird. Zu diesem Zeitpunkt ist der Import erfolgreich abgeschlossen. (Möglicherweise müssen Sie diese Änderung in einem oder in beiden Modulen vornehmen, je nachdem, welches zuerst importiert wurde.)
Das Design scheint jedoch etwas seltsam zu sein. Ihr Design ist so, dass die Klassen User
und Story
sehr eng gekoppelt sind - keines kann ohne das andere verwendet werden. In einem solchen Fall wäre es normalerweise sinnvoller, beide im selben Modul zu haben.
Die offensichtlichste Lösung in diesem Fall besteht darin, die Abhängigkeit vollständig von der Klasse User
zu trennen, indem Sie die Schnittstelle so ändern, dass der Konstruktor Story
eine tatsächliche User
akzeptiert, nicht eine user_id
. Dies führt auch zu einem effizienteren Design: Wenn ein Benutzer beispielsweise viele Storys hat, kann allen Konstruktoren dasselbe Objekt zugewiesen werden.
Ansonsten sollte der Import eines ganzen Moduls (also story
und user
anstelle der Mitglieder) funktionieren - das zuerst importierte Modul wird zum Zeitpunkt des zweiten Imports leer angezeigt; Es spielt jedoch keine Rolle, da der Inhalt dieser Module nicht im globalen Gültigkeitsbereich verwendet wird.
Dies ist gegenüber dem Import innerhalb einer Methode etwas vorzuziehen. Der Import innerhalb einer Methode hat einen erheblichen Mehraufwand gegenüber nur einer modulübergreifenden Suche ( story.Story
), da sie für jeden Methodenaufruf durchgeführt werden muss. scheint, dass in einem einfachen Fall der Overhead mindestens 30-fach ist.
Es gibt einige dieser python
zirkulären Importfragen im Web. Ich habe mich dazu entschieden, zu diesem Thread beizutragen, weil die Anfrage einen Kommentar von Ray Hettinger enthält, der den Anwendungsfall eines zirkulären Imports legitimiert, aber eine Lösung empfiehlt, die meiner Meinung nach keine besonders gute Praxis darstellt - den Import in eine Methode zu verschieben.
Abgesehen von Hettingers Autorität sind drei Disclaimer zu gemeinsamen Einwänden notwendig:
Außerdem glaube ich, dass Wartbarkeit und Lesbarkeit dazu führen, dass Importe an der Spitze der Datei gruppiert werden, nur einmal für jeden benötigten Namen vorkommen und dass der Stil from module import name
vorzuziehen ist (außer vielleicht für sehr kurze Modulnamen mit vielen) Funktionen, zB gtk
), da es repetitives verbales Durcheinander vermeidet und Abhängigkeiten explizit macht.
Damit werde ich eine vereinfachte Version meines eigenen Anwendungsfalls vorschlagen, der mich hierher gebracht hat, und meine Lösung bereitstellen.
Ich habe zwei Module, die jeweils viele Klassen definieren. surface
definiert geometrische Flächen wie Ebenen, Kugeln, Hyperboloide usw. path
definiert planare geometrische Figuren wie Linien, Kreishyperbeln usw. Logischerweise sind dies verschiedene Kategorien und Refactoring ist aus Sicht der API-Anforderungen keine Option. Dennoch sind diese beiden Kategorien intim.
Eine nützliche Operation schneidet zwei Oberflächen, zum Beispiel ist der Schnittpunkt zweier Ebenen eine Linie, oder der Schnitt einer Ebene und einer Kugel ist ein Kreis.
Wenn Sie zum Beispiel in surface.py
den direkten Import durchführen, um den Rückgabewert für eine Kreuzungsoperation zu implementieren:
Sie erhalten:
%Vor%Geometrisch werden Ebenen verwendet, um die Pfade zu definieren, schließlich können sie beliebig in drei (oder mehr) Dimensionen ausgerichtet sein. Das Traceback teilt Ihnen sowohl was passiert und die Lösung.
Ersetzen Sie einfach die Importanweisung in surface.py
durch:
Die Abfolge der Operationen in der Rückverfolgung läuft noch. Es ist nur so, dass wir beim zweiten Mal den Importfehler ignorieren. Dies spielt keine Rolle, da Line
nicht auf Modulebene verwendet wird. Daher wird der erforderliche Namespace von surface
in path
geladen. Das Namespace-Parsing von path
kann daher abgeschlossen werden, sodass es in surface
geladen werden kann und die erste Begegnung mit from path import Line
abgeschlossen wird. Daher kann das Namespace-Parsing von surface
fortfahren und vervollständigen und zu dem weiterführen, was sonst noch notwendig ist.
Es ist ein einfaches und sehr klares Idiom. Die try: ... except ...
-Syntax dokumentiert das zirkuläre Importproblem klar und prägnant und erleichtert zukünftige Wartungsarbeiten. Verwenden Sie es immer, wenn ein Refactor wirklich eine schlechte Idee ist.
Wie BrenBarn sagte, ist die naheliegendste Lösung, Benutzer und Story im selben Modul zu halten, was sehr sinnvoll ist, wenn der User etwas über Story wissen soll. Nun, wenn Sie wirklich brauchen, um sie in verschiedenen Modulen zu haben, können Sie auch den Benutzer in story.py anpassen, um die Methode get_stories
hinzuzufügen. Es ist ein Lesbarkeits- / Entkopplungs-Trade-Off ...
Tags und Links python design import circular-dependency