2010-10-17 3 views
41

Python (3.x) Skripte Lassen Sie betrachten:Python Kreis Importe wieder einmal (auch bekannt als was mit diesem Entwurf nicht stimmt)

main.py:

from test.team import team 
from test.user import user 

if __name__ == '__main__': 
    u = user() 
    t = team() 
    u.setTeam(t) 
    t.setLeader(u) 

Test/user.py:

from test.team import team 

class user: 
    def setTeam(self, t): 
     if issubclass(t, team.__class__): 
      self.team = t 

Test/team.py:

from test.user import user 

class team: 
    def setLeader(self, u): 
     if issubclass(u, user.__class__): 
      self.leader = u 

Nein Natürlich habe ich kreisförmigen Import und großartigen ImportError.

Also, nicht Pythonista sein, habe ich drei Fragen. Zunächst einmal:

i. Wie kann ich das Ding zum Laufen bringen?

Und zu wissen, dass jemand wird unweigerlich sagen "Circular Importe zeigen immer ein Design-Problem", die zweite Frage kommt:

ii. Warum ist dieses Design schlecht?

Und schließlich drittens ein:

iii. Was wäre eine bessere Alternative?

Um genau zu sein, Typprüfung wie oben ist nur ein Beispiel, es gibt auch eine Indexschicht basierend auf Klasse, die erlaubt, dh. Hier finden Sie alle Benutzer Mitglieder eines Teams (Benutzerklasse hat viele Unterklassen, so Index verdoppelt wird, für die Nutzer im Allgemeinen und für jede spezifische Unterklasse) sein oder alle Teams bestimmten Benutzer als Mitglied mit

Edit:

Ich hoffe, dass detailliertere Beispiele klären, was ich versuche zu erreichen. Dateien für bessere Lesbarkeit weggelassen (aber eine 300kb Quelldatei mit schreckt mich irgendwie, so übernehmen Sie bitte, dass jede Klasse in verschiedenen Datei)

# ENTITY 

class Entity: 
    _id = None 
    _defs = {} 
    _data = None 

    def __init__(self, **kwargs): 
     self._id = uuid.uuid4() # for example. or randint(). or x+1. 
     self._data = {}.update(kwargs) 

    def __settattr__(self, name, value): 
     if name in self._defs: 
      if issubclass(value.__class__, self._defs[name]): 
       self._data[name] = value 

       # more stuff goes here, specially indexing dependencies, so we can 
       # do Index(some_class, name_of_property, some.object) to find all 
       # objects of some_class or its children where 
       # given property == some.object 

      else: 
       raise Exception('Some misleading message') 
     else: 
      self.__dict__[name] = value  

    def __gettattr__(self, name): 
     return self._data[name] 

# USERS 

class User(Entity): 
    _defs = {'team':Team} 

class DPLUser(User): 
    _defs = {'team':DPLTeam} 

class PythonUser(DPLUser) 
    pass 

class PerlUser(DPLUser) 
    pass 

class FunctionalUser(User): 
    _defs = {'team':FunctionalTeam} 

class HaskellUser(FunctionalUser) 
    pass 

class ErlangUser(FunctionalUser) 
    pass 

# TEAMS 

class Team(Entity): 
    _defs = {'leader':User} 

class DPLTeam(Team): 
    _defs = {'leader':DPLUser} 

class FunctionalTeam(Team): 
    _defs = {'leader':FunctionalUser} 

und jetzt einige Nutzung:

t1 = FunctionalTeam() 
t2 = DLPTeam() 
t3 = Team() 

u1 = HaskellUser() 
u2 = PythonUser() 

t1.leader = u1 # ok 
t2.leader = u2 # ok 
t1.leader = u2 # not ok, exception 
t3.leader = u2 # ok 

# now , index 

print(Index(FunctionalTeam, 'leader', u2)) # -> [t2] 
print(Index(Team, 'leader', u2)) # -> [t2,t3] 

Also, es funktioniert super (Implementierungsdetails sind weggelassen, aber es gibt nichts Kompliziertes) abgesehen von diesem unheiligen zirkulären Import-Ding.

+2

Es wird als gute Übung angesehen, Ihre Klassen zu kapitalisieren - Team/Benutzer. – snapshoe

+4

Außerdem: Auschecken Python [Eigenschaften] (http://docs.python.org/library/functions.html#property) für die bevorzugte Alternative zu Setter und Getters zu deklarieren. – intuited

+0

@intuited: ich liebe Dekorateure, aber anscheinend funktioniert es nicht großartig mit __setattr__/__getattr__ (Beispiel oben ist eher vereinfacht) –

Antwort

77

Kreis Importe Sache nicht von Natur aus schlecht sind Es ist natürlich für den team Code auf user während der user verlassen haben etwas mit team.

Die schlechtere Praxis ist hier from module import member. Das Modul team versucht, die Klasse user zum Zeitpunkt des Imports zu erhalten, und das Modul user versucht, die Klasse team zu erhalten. Aber die Klasse team existiert noch nicht, weil Sie immer noch in der ersten Zeile von team.py sind, wenn user.py ausgeführt wird.

Importieren Sie stattdessen nur Module. Dies führt zu einem klareren Namespacing, macht späteres Affepatching möglich und löst das Importproblem. Da Sie nur das Modul importieren Import-Zeit, ist es Ihnen egal, die Klasse innerhalb es ist noch nicht definiert. Wenn Sie mit dem Unterricht beginnen, wird es sein.

So Test/users.py:

import test.teams 

class User: 
    def setTeam(self, t): 
     if isinstance(t, test.teams.Team): 
      self.team = t 

Test/teams.py:

import test.users 

class Team: 
    def setLeader(self, u): 
     if isinstance(u, test.users.User): 
      self.leader = u 

from test import teams und dann teams.Team ist auch OK, wenn Sie test weniger schreiben möchten. Das importiert immer noch ein Modul und kein Modulmitglied.

Auch, wenn Team und User relativ einfach sind, legen Sie sie in das gleiche Modul. Sie müssen nicht dem Java-i-Klasse-pro-Datei-Idiom folgen. Die Methoden isinstance testen und set schreien auch unpythonisch-Java-warzen zu mir; Je nachdem, was Sie tun, können Sie besser eine einfache, nicht typgeprüfte verwenden.

+9

Rundschreiben Importe sind von Natur aus eine schlechte Sache.Wenn Sie jemals einen Teil Ihrer Anwendung nehmen und z. B. eine Bibliothek daraus machen wollen, dann ist das Wichtigste an den Abhängigkeiten dieses Blocks, dass sie alle von der App zur Bibliothek gehen müssen. Eine Bibliothek, die von Ihrer App abhängig ist, ist für niemanden nützlich. Nicht einmal Sie - Sie werden keine Tests in der Bibliothek durchführen können, ohne sie mit der App zu bündeln, was den Zweck, sie überhaupt zu entkoppeln, zunichte macht. Da zirkuläre Abhängigkeiten beide Wege gehen, ist es unmöglich, den Code jemals in entkoppelte Blöcke aufzuteilen. –

+13

Lassen Sie mich das für Sie rückgängig machen: Rundschreiben-Importe sind inhärent eine schlechte Sache, _wenn Sie jemals einen Teil Ihrer Anwendung nehmen und z. B. eine Bibliothek daraus machen wollen. _In einem entkoppelten Chunk kann es durchaus sinnvoll sein, Zirkelbezüge zu verwenden. – mhsmith

+17

"Inhärent" hat einen schlechten Geruch. Es ist wie "offensichtlich" oder "sicher" zu sagen. Wenn die Welt zirkuläre Abhängigkeiten hat und Sie Klassen verwenden, um die Welt zu modellieren, dann haben Ihre Klassen möglicherweise zirkuläre Abhängigkeiten. Ja, auf der Implementierungsebene machen sie es schwierig und es lohnt sich, Lösungen zu diskutieren. – bootchk

3

i. Damit es funktioniert, können Sie einen verzögerten Import verwenden. Eine Möglichkeit wäre, user.py in Ruhe zu lassen und team.py zu ändern in:

class team: 
    def setLeader(self, u): 
     from test.user import user 
     if issubclass(u, user.__class__): 
      self.leader = u 

iii. Für eine Alternative, warum nicht die Team-und Benutzer-Klassen in der gleichen Datei?

+1

ad. iii - Ich habe 60+ solche Klassen und setzen sie in eine Datei ist nicht wirklich die Option –

+0

ad i. - Ist das nicht Leistung? Weiß jemand, ob Python diese Art von Importen intern optimiert? –

+3

ja, es ist optimiert. siehe http://docs.python.org/library/sys.html#sys.modules – snapshoe

2

Bad Praxis/sind stinkende folgenden Dinge:

  • woanders vermutlich unnötige Typprüfung (see also here). Verwenden Sie einfach die Objekte, die Sie als Benutzer/Team erhalten, und lösen Sie eine Ausnahme aus (oder in den meisten Fällen wird eine ausgelöst, ohne dass zusätzlicher Code benötigt wird), wenn sie bricht. Lassen Sie das weg, und Sie zirkuläre Importe gehen weg (zumindest für jetzt). Solange die Objekte sich wie ein Benutzer/ein Team verhalten, könnten sie alles Mögliche sein.(Duck Typing)
  • kleingeschrieben Klassen (das ist mehr oder weniger eine Frage des Geschmacks, sondern der allgemein akzeptierte Standard (PEP 8) macht es anders
  • Setter wo nicht notwendig: Sie könnten nur sagen: my_team.leader=user_b und user_b.team=my_team
  • Probleme mit der Datenkonsistenz:. was ist, wenn (my_team.leader.team!=my_team)
+0

In der Realität waren Getter und Setter viel einfacher. Normalerweise nehmen sie mehr Verantwortung, dh die Datenkonsistenz zu überprüfen (i Ich poste hier nicht den ganzen Code). Kleinbuchstaben - ok, ich lese gerade PEP8;). Last but not least - Typprüfung. Das Problem ist, dass Objekte, die hier gesetzt werden, propagiert werden und ihre Verwendung oft aufgeschoben wird. Also, wenn das Objekt einen falschen Typ hat, sollte ich alle Fortpflanzungen zurückverfolgen, was ziemlich unmöglich sein kann. Stattdessen validiere ich vorher das Objekt (ja, verletze EAFP), und die Validierung basiert auf der Klasse des Objekts und nicht auf den Eigenschaften. Irgendeine vernünftige Lösung hier? –

+0

Sie könnten Ihren Instanzen einfach ein Mitglied hinzufügen, indem Sie sagen, welche Klasse es ist: Dann würde Ihr Test wie folgt aussehen: 'Wenn nicht u.is_a ==" user ":' was wäre dann nur eine Plausibilitätsprüfung mit Ducken. –

+0

@Michael: es sieht so aus, dass ich damit umgehen muss, obwohl dies die Klassenvererbungsstruktur verdoppelt –

1

Hier ist etwas, was ich noch nicht gesehen habe. Ist es eine schlechte Idee/Design mit sys.modules direkt? Nach dem Lesen der @bobince-Lösung dachte ich, ich hätte das gesamte Importgeschäft verstanden, aber dann stieß ich auf ein Problem, das einem question ähnelt, der mit diesem verknüpft ist.

Hier ist ein weiteres übernehmen die Lösung:

# main.py 
from test import team 
from test import user 

if __name__ == '__main__': 
    u = user.User() 
    t = team.Team() 
    u.setTeam(t) 
    t.setLeader(u) 

# test/team.py 
from test import user 

class Team: 
    def setLeader(self, u): 
     if isinstance(u, user.User): 
      self.leader = u 

# test/user.py 
import sys 
team = sys.modules['test.team'] 

class User: 
    def setTeam(self, t): 
     if isinstance(t, team.Team): 
      self.team = t 

und die Datei test/__init__.py Datei leer ist. Der Grund dafür ist, dass test.team zuerst importiert wird. Sobald Python eine Datei importiert/liest, hängt es das Modul an sys.modules an.Wenn wir test/user.py importieren, wird das Modul test.team bereits definiert, da wir es in main.py importieren.

Ich mag diese Idee für Module, die ziemlich groß werden, aber es gibt Funktionen und Klassen, die voneinander abhängen. Nehmen wir an, dass es eine Datei namens util.py gibt und diese Datei viele Klassen enthält, die voneinander abhängen. Vielleicht könnten wir den Code auf verschiedene Dateien aufteilen, die voneinander abhängen. Wie kommen wir um den kreisförmigen Import herum?

Nun, in der util.py Datei, die Objekte von den anderen „privaten“ Dateien alles, was wir einfach importieren, ich privat sagen, da diese Dateien nicht direkt zugegriffen werden sollen, sondern wir sie durch die ursprüngliche Datei zugreifen:

# mymodule/util.py 
from mymodule.private_util1 import Class1 
from mymodule.private_util2 import Class2 
from mymodule.private_util3 import Class3 

Dann auf jeder der anderen Dateien:

# mymodule/private_util1.py 
import sys 
util = sys.modules['mymodule.util'] 
class Class1(object): 
    # code using other classes: util.Class2, util.Class3, etc 

# mymodule/private_util2.py 
import sys 
util = sys.modules['mymodule.util'] 
class Class2(object): 
    # code using other classes: util.Class1, util.Class3, etc 

Die sys.modules Anruf funktioniert, solange versucht wird, die mymodule.util zuerst zu importieren.

Schließlich möchte ich nur darauf hinweisen, dass dies getan wird, um Benutzern mit Lesbarkeit zu helfen (kürzere Dateien) und damit würde ich nicht sagen, dass zirkuläre Importe "von Natur aus" schlecht sind. Alles hätte in der gleichen Datei gemacht werden können, aber wir verwenden dies, damit wir den Code trennen können und uns nicht verwirren, während wir durch die riesige Datei scrollen.

Verwandte Themen