2013-11-15 7 views
8

user.py:Entfernen Python Kreis import

from story import Story 

class User: 
    ... 
    def get_stories(self): 
     story_ids = [select from database] 
     return [Story.get_by_id(id) for id in story_ids] 

story.py

from user import User 

class Story: 
    ... 
    def __init__(self, id, user_id, content): 
     self.id = id 
     self.user = User.get_by_id(user_id) 
     self.content = content 

wie Sie sehen können, gibt es einen kreisförmigen Import in diesem Programm, das 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)?

+0

Es ist nicht notwendig, den kreisförmigen Import für gutes Design zu entfernen. Das Verschieben des Imports in eine Methodendefinition ist eine sinnvolle Möglichkeit, einen Import zu verschieben. –

Antwort

1

Eine weitere Möglichkeit, die Kreisförmigkeit zu verringern, besteht darin, den Importstil zu ändern. Ändern Sie from story import Story zu import story, dann beziehen Sie sich 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 Entwurf ist so, dass die Klassen User und Story sehr eng gekoppelt sind - keiner kann ohne den anderen verwendet werden. In einem solchen Fall wäre es normalerweise sinnvoller, beide im selben Modul zu haben.

+0

Ja, ich finde, dass dieser Entwurf auch etwas seltsam ist ... da dies eine allgemeine Situation ist, frage ich mich, wie ein gutes Design aussehen sollte? Danke – wong2

+1

Nun, das ist __istnot eine gemeinsame Situation :) –

+0

Vorgeschlagene Änderung wird das Problem nicht beheben, wird es immer noch einen kreisförmigen Import. –

0

Wie BrenBarn sagte, ist die naheliegendste Lösung, User und Story im selben Modul zu halten, was sehr sinnvoll ist, wenn der User etwas über Story wissen soll. Jetzt, wenn Sie wirklich brauchen, um sie in verschiedenen Modulen zu haben, können Sie auch monkeypatch Benutzer in story.py die get_stories Methode hinzufügen. Es ist ein Lesbarkeit/Entkopplung Trade-off ...

1

Die naheliegendste Lösung ist in diesem Fall die Abhängigkeit zu der User Klasse zu brechen, vollständig durch die Schnittstelle zu ändern, so dass der Story Konstruktor ein tatsächliches User akzeptiert, keine user_id. Dies führt auch zu einem effizienteren Design: Wenn beispielsweise ein Benutzer viele Storys hat, kann allen Konstruktoren das gleiche Objekt gegeben werden.

Ansonsten sollte der Import eines ganzen Moduls (das sind 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 Umfang 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 einem modulübergreifenden Lookup (story.Story), da dies für jeden Methodenaufruf durchgeführt werden muss. scheint, dass in einem einfachen Fall der Overhead mindestens 30-fach ist.

1

Es gibt eine Reihe von diesen python kreisförmige Import Fragen im Internet. 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 Übung ist - den Import in eine Methode zu verschieben.

Neben Hettinger Autorität, sind drei Haftungsausschluss zu gemeinsamen Einwand notwendig:

  1. Ich habe noch nie in Java programmiert. Ich versuche nicht Java zu machen.
  2. Refactoring ist nicht immer nützlich oder effektiv.Die logische API gibt manchmal eine Struktur vor, die rekursive Importverweise unvermeidbar macht. Denken Sie daran, Code existiert für Benutzer, nicht für Programmierer.
  3. Das Kombinieren von Modulen, die ziemlich groß sind, kann Probleme mit der Lesbarkeit und Wartbarkeit verursachen, die viel schlimmer sein können als ein oder zwei rekursive Importe.

Darüber hinaus glaube ich, Wartbarkeit und Lesbarkeit schreibt vor, dass die Einfuhren an der Spitze der Datei gruppiert werden, treten nur einmal für jeden benötigten Namen, und dass die from module import name Stil bevorzugt, (außer vielleicht für sehr kurze Modulnamen mit viele Funktionen, zB gtk), da es sich wiederholende verbale Unordnung vermeidet und Abhängigkeiten explizit macht.

Damit werde ich eine vereinfachte Version meines eigenen Anwendungsfalles, der mich hierher gebracht hat, vorschlagen und meine Lösung anbieten.

Ich habe zwei Module, die jeweils viele Klassen definieren. surface definiert geometrische Oberflächen wie Ebenen, Kugeln, Hyperboloide usw. path definiert planare geometrische Figuren wie Linien, Kreise, Hyperbeln usw. Logischerweise sind dies verschiedene Kategorien und Refactoring ist keine Option aus Sicht der API-Anforderungen. 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 Schnittpunkt einer Ebene und einer Kugel ist ein Kreis.

Wenn zum Beispiel in surface.py Sie geradeaus Import tun musste, um den Rückgabewert für einen Schnittbetrieb implementieren:

from path import Line 

Sie erhalten:

Traceback (most recent call last): 
    File "surface.py", line 62, in <module> 
    from path import Line 
    File ".../path.py", line 25, in <module> 
    from surface import Plane 
    File ".../surface.py", line 62, in <module> 
    from path import Line 
ImportError: cannot import name Line 

Geometrisch sind Flugzeuge verwendet Definieren Sie die Pfade, schließlich können sie beliebig in drei (oder mehr) Dimensionen orientiert sein. Das Traceback teilt Ihnen mit, was passiert und welche Lösung es gibt.

Ersetzen Sie einfach die Import-Anweisung in surface.py mit:

try: from path import Line 
except ImportError: pass # skip circular import second pass 

die Folge von Operationen in der Spur zurück noch geschieht. 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 surface in path geladen. Das Namespace-Parsing von path kann daher abgeschlossen werden, wodurch es möglich ist, es in surface zu laden und die erste Begegnung mit from path import Line abzuschließen. Daher kann das Namespace-Parsing von surface fortfahren und vervollständigen, was auch immer sonst notwendig ist.

Es ist ein einfaches und sehr klares Idiom. Die try: ... except ... Syntax dokumentiert klar und prägnant das zirkuläre Importproblem und erleichtert zukünftige Wartungsarbeiten. Verwenden Sie es immer, wenn ein Refactor wirklich eine schlechte Idee ist.