2012-08-04 9 views
5

Kürzlich las ich an interesting discussion on how to make a singleton in Python. Eine der Lösungen war ein tricky decorator defining a class inside its code as a substitute for decorated class:Warum passiert hier eine Rekursion?

def singleton(class_): 
    class class_w(class_): 
     _instance = None 
     def __new__(class2, *args, **kwargs): 
      if class_w._instance is None: 
       class_w._instance = super(class_w, class2).__new__(class2, *args, **kwargs) 
       class_w._instance._sealed = False 
      return class_w._instance 
     def __init__(self, *args, **kwargs): 
      if self._sealed: 
       return 
      super(class_w, self).__init__(*args, **kwargs) 
      self._sealed = True 
    class_w.__name__ = class_.__name__ 
    return class_w 

@singleton 
class MyClass(object): 
    def __init__(self, text): 
     print text 
    @classmethod 
    def name(class_): 
     print class_.__name__ 

x = MyClass(111) 
x.name() 
y = MyClass(222) 
print id(x) == id(y) 

Ausgang ist:

111  # the __init__ is called only on the 1st time 
MyClass # the __name__ is preserved 
True # this is actually the same instance 

Es angegeben ist, dass, wenn wir super(MyClass, self).__init__(text) innerhalb __init__ von MyClass verwenden wir in Rekursion bekommen.

Ich habe getestet und in der Tat die Rekursion passiert. Aber, wie ich sie verstehe, MyClass erbt object, nur super(MyClass, self) sollte so einfach object sein, aber es stellt sich heraus, dass super(MyClass, self) ist __main__.MyClass

Können Sie uns erklären, was für Schritt hier Schritt geschieht für mich, die Gründe zu verstehen, warum die Rekursion geschieht ?

+1

Super() ist ein bisschen schwierig mit Python, überprüfen http://rhettinger.wordpress.com/2011/05/26/super-considered-super/ – Lycha

Antwort

5

Das Problem ist, dass Sie mit Schreiben super(MyClass, self).__init__(text) sagen, dass die Super relativ zu was auch immer MyClasssuper genannt wird. Aber der Decorator ersetzt MyClass durch eine Unterklasse von sich. Also, wenn Ihre ursprüngliche __init__ Methode heißt MyClass bezieht sich eigentlich auf eine Unterklasse der Klasse, die die ausführende Methode definiert.

Um es Schritt für Schritt zu sagen, werde ich die ursprüngliche Klasse anrufen (wie in der Quelle geschrieben) OrigMyClass, und die resultierende Version (nach dem Dekorator) DecMyClass. Ich werde MyClass nur als eine Variable verwenden, weil sich ihre Bedeutung während der Ausführung ändert.

  1. Sie definieren eine __init__ Methode auf OrigMyClass, aber die __init__ Methode aufruft super(MyClass, self), nicht super(OrigMyClass, self). Welche Methode tatsächlich aufgerufen wird, hängt also davon ab, was sich MyClass auf zu dem Zeitpunkt bezieht, zu dem die Methode aufgerufen wird. Der Wert von MyClass wird zur Ausführungszeit wie jede andere Variable nachgeschlagen; Platzieren Sie es innerhalb der super Aufruf oder innerhalb der __init__ Methode bindet es nicht magisch an die Klasse, die es zufällig ist, wenn Sie es schreiben; Variablen in Funktionen werden ausgewertet, wenn sie aufgerufen werden, nicht wenn sie definiert sind.

  2. Der Dekorateur läuft. Der Dekorator definiert eine neue Klasse DecMyClass als eine Unterklasse von OrigMyClass. DecMyClass definiert eine __init__, die super(DecMyClass, self) aufruft.

  3. Nach dem Ausführen des Dekorators ist der Name MyClass an die Klasse DecMyClass gebunden. Beachten Sie, dass dies bedeutet, dass, wenn der Aufruf super(MyClass, self) später ausgeführt wird, es super(DecMyClass, self) tun wird. Wenn Sie MyClass(111) tun, instanziieren Sie ein Objekt DecMyClass. DecMyClass.__init__ Anrufe super(DecMyClass, self).__init__. Dies führt OrigMyClass.__init__ aus.

  4. OrigMyClass.__init__ Anrufe super(MyClass, self).__init__.Da sich MyClass auf DecMyClass bezieht, ist dies das gleiche wie super(DecMyClass, self).__init__. Aber DecMyClass ist eine Unterklasse von OrigMyClass. Der entscheidende Punkt ist, dass, weil MyClass sich auf DecMyClass bezieht, OrigMyClass tatsächlich Super auf einer Unterklasse von sich aufruft.

  5. Also wieder super(DecMyClass, self).__init__ ruft OrigMyClass.__init__ auf, die sich selbst wieder aufruft, und so weiter bis ins Unendliche.

Der Effekt ist der gleiche wie dieser Code, der die Ausführungspfad deutlicher machen kann:

>>> class Super(object): 
...  def __init__(self): 
...   print "In super init" 
...   super(Sub, self).__init__() 
>>> class Sub(Super): 
...  def __init__(self): 
...   print "In sub init" 
...   super(Sub, self).__init__() 

Beachten Sie, dass Super ruft super(Sub, self). Es versucht, eine Superklassenmethode aufzurufen, versucht jedoch, die Superklassenmethode Sub aufzurufen. Die Oberklasse von Sub ist Super, also Super endet mit dem Aufruf seiner eigenen Methode wieder.

Edit: Nur die Namen-Lookup-Fragen zu klären Sie angesprochen, hier ist ein anderes etwas anderes Beispiel, das das gleiche Ergebnis:

>>> class Super(object): 
...  def __init__(self): 
...   print "In super init" 
...   super(someClass, self).__init__() 
>>> class Sub(Super): 
...  def __init__(self): 
...   print "In sub init" 
...   super(Sub, self).__init__() 
>>> someClass = Sub 

Dies sollte deutlich machen, dass die Klasse Argument super (das erste Argument , hier someClass) ist in keiner Weise besonders. Es ist nur ein gewöhnlicher Name, dessen Wert zur gewöhnlichen Zeit auf gewöhnliche Weise nachgeschlagen wird, nämlich wenn der Aufruf super ausgeführt wird. Wie in diesem Beispiel gezeigt, muss die Variable nicht einmal zum Zeitpunkt der Definition der Methode existieren. Der Wert wird zu dem Zeitpunkt gesucht, als Sie die Methode aufrufen.

+0

Oh. Es war schwer zu verstehen. Aber es scheint mir, dass ich es bekommen habe! Der Schlüssel, den ich nicht verstanden habe, war der, warum 'super (MyClass, self)' sich nicht immer auf 'OrigMyClass' bezieht, sondern sich irgendwann (nach der Dekoration) auf' DecMyClass' bezieht. Und schließlich bekam ich, dass "__init__" nur die logischen Bereiche nachschlägt, bis es "MyClass" im Modulbereich findet, wo es sich auf "DecMyClass" bezieht. Vor diesem Verständnis dachte ich, dass "__init__" in 'MyClass' lebt, also sollte es immer an die ursprüngliche Klasse' OrigMyClass' gebunden sein. Jetzt ist alles klar! Vielen Dank! – ovgolovin

+0

@ovgolovin: Ja, aber es ist nichts wert, dass dies das gleiche ist, was * immer * passiert. Jeder Verweis auf einen beliebigen Namen in einer Funktion wird immer auf diese Weise nachgeschlagen (es sei denn, der Name wird zugewiesen). Es ist nichts spezifisch für "Super". – BrenBarn

+0

@ovgolovin: Ich habe ein weiteres kleines Beispiel hinzugefügt, um dies zu verdeutlichen. – BrenBarn

Verwandte Themen