2009-03-03 10 views
46

Ich führe Python 2.5, so dass diese Frage möglicherweise nicht auf Python 3 zutrifft. Wenn Sie eine Diamant-Klassenhierarchie mit Mehrfachvererbung erstellen und ein Objekt der abgeleiteten Klasse erstellen, macht Python das Right Thing (TM). Es ruft den Konstruktor für die am weitesten abgeleitete Klasse auf, dann die übergeordneten Klassen von links nach rechts, dann das Großelternteil. Ich kenne Pythons MRO; das ist nicht meine Frage. Ich bin gespannt, wie das Objekt von Super tatsächlich erreicht, um Aufrufe von Super in den Elternklassen die richtige Reihenfolge zu kommunizieren. Betrachten Sie dieses Beispiel-Code:Wie macht Pythons "Super" das Richtige?

#!/usr/bin/python 

class A(object): 
    def __init__(self): print "A init" 

class B(A): 
    def __init__(self): 
     print "B init" 
     super(B, self).__init__() 

class C(A): 
    def __init__(self): 
     print "C init" 
     super(C, self).__init__() 

class D(B, C): 
    def __init__(self): 
     print "D init" 
     super(D, self).__init__() 

x = D() 

Der Code ist die intuitive Sache, es druckt:

D init 
B init 
C init 
A init 

Wenn Sie jedoch den Aufruf von super in B init-Funktion auf Kommentar, weder A noch C der init-Funktion wird genannt. Das bedeutet, dass Bs Aufruf an Super irgendwie die Existenz von C in der gesamten Klassenhierarchie kennt. Ich weiß, dass super ein Proxy-Objekt mit einem überladenen get-Operator zurückgibt, aber wie teilt das Objekt, das von super in D'init-Definition zurückgegeben wird, die Existenz von C an das von super in Bs init-Definition zurückgegebene Objekt mit? Ist die Information, dass nachfolgende Aufrufe von super use auf dem Objekt selbst gespeichert sind? Wenn ja, warum ist nicht super statt selbst.super?

Edit: Jekke hat ganz richtig darauf hingewiesen, dass es nicht self.super ist, weil super ein Attribut der Klasse ist, keine Instanz der Klasse. Konzeptionell macht das Sinn, aber in der Praxis ist Super auch kein Attribut der Klasse! Sie können dies im Interpreter testen, indem Sie zwei Klassen A und B erstellen, wobei B von A erbt und dir(B) aufruft. Es hat keine super oder __super__ Attribute.

+0

Super ist nicht self.super, weil super eine Eigenschaft der Klasse ist, nicht die Instanz. (Ich verstehe den Rest der Frage nicht wirklich.) –

+0

Irrelevant, vielleicht; aber ich wurde von mehreren Leuten darauf hingewiesen, Python 2.5 nicht mehr zu benutzen, da Python 3 so viele neue Funktionen/Fehler enthält, dass es so viele Bugs gibt. – adam

+0

Editieren zu 'Wie teilt das Objekt, das von super in D'init-Definition zurückgegeben wird, die Existenz von C zu dem Objekt, das von Super in Bs Init-Definition zurückgegeben wird'? Ich glaube, das ist, was Sie fragen - es könnte mehr Sinn machen :) –

Antwort

14

Ich habe unten eine Reihe von Links zur Verfügung gestellt, die Ihre Frage detaillierter und genauer beantworten, als ich jemals hoffen kann. Ich werde jedoch auch in meinen eigenen Worten eine Antwort auf Ihre Frage geben, um Ihnen Zeit zu sparen. Ich werde es in Punkte setzen -

  1. Super ist eine eingebaute Funktion, kein Attribut.
  2. Jeder Typ (Klasse) in Python hat ein __mro__ Attribut, das die Methodenauflösungsreihenfolge dieser bestimmten Instanz speichert.
  3. Jeder Aufruf von Super hat die Form super (Typ [, Objekt oder Typ]). Nehmen wir an, dass das zweite Attribut ein Objekt für den Moment ist.
  4. Am Startpunkt von Superaufrufen ist das Objekt vom Typ der abgeleiteten Klasse (, zB DC).
  5. Super sucht nach Methoden, die (in Ihrem Fall __init__) in den Klassen in der MRO entsprechen, nach der Klasse als das erste Argument angegeben (in diesem Fall Klassen nach DC).
  6. Wenn die passende Methode gefunden wird (zB in Klasse BC1), wird es aufgerufen.
    (Diese Methode sollte super verwenden, also nehme ich an, dass es tut - Siehe Python ist super, aber kann nicht verwendet werden - Link unten) Diese Methode verursacht dann eine Suche in der Klasse des Objekts 'MRO für die nächste Methode, zu das Recht von BC1.
  7. Spülvorgang wiederholen, bis alle Methoden gefunden und aufgerufen sind.

Erklärung für Ihr Beispiel

MRO: D,B,C,A,object 
  1. super(D, self).__init__() aufgerufen. isinstance (self, D) => True
  2. Suche nach nächste Methode in der MRO in Klassen rechts von D.

    B.__init__ gefunden und rief

  3. B.__init__ Anrufe super(B, self).__init__().

    isinstance (self, B) => False
    isinstance (self, D) => True

  4. Damit ist die MRO die gleiche, aber die Suche geht weiter nach rechts von B dh C, A , Objekt wird eins nach dem anderen durchsucht. Der nächste __init__ gefunden wird aufgerufen.

  5. Und so weiter und so fort.

Eine Erklärung Super
http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
Dinge zu beobachten, wenn Super
http://fuhm.net/super-harmful/
Pythons MRO-Algorithmus:
http://www.python.org/download/releases/2.3/mro/
docs Super:
http://docs.python.org/library/functions.html
Die unten auf dieser Seite hat einen schönen Abschnitt auf super:
http://docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-5-sect-2.html

Ich hoffe, dies klar hilft es auf.

+0

in Ihrer Erklärung, verstehe ich nicht den 3. Punkt "isinstance (selbst, B) => False", warum die Suche fortgesetzt? – Alcott

6

nur raten:

self in allen vier Verfahren auf das gleiche Objekt beziehen, das heißt, der Klasse D. so, in B.__init__(), der Anruf an super(B,self) kennt die gesamte Diamant Abstammung von self und es muss die Methode von "nach" B holen. In diesem Fall ist es die Klasse C.

+0

' self' auf die Instanz der enthaltenden Klasse beziehen sollte, und nicht auf eine andere Klasse, nein? –

+1

nein, wenn Sie eine 'self.method()' aufrufen, wäre es die spezifischste Implementierung, egal wie hoch Sie sind. das ist die Essenz des Polymorphismus – Javier

34

Code Um dies zu ändern, und ich denke, dass es Dinge erklären werde (vermutlich super betrachtet, wo, sagen wir, B im __mro__ ist?):

class A(object): 
    def __init__(self): 
     print "A init" 
     print self.__class__.__mro__ 

class B(A): 
    def __init__(self): 
     print "B init" 
     print self.__class__.__mro__ 
     super(B, self).__init__() 

class C(A): 
    def __init__(self): 
     print "C init" 
     print self.__class__.__mro__ 
     super(C, self).__init__() 

class D(B, C): 
    def __init__(self): 
     print "D init" 
     print self.__class__.__mro__ 
     super(D, self).__init__() 

x = D() 

Wenn Sie es laufen Sie werden sehen, :

D init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 
B init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 
C init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 
A init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 

auch ist es wert Python's Super is nifty, but you can't use it Check-out.

+2

, wie ich es schon vermutet habe: Selbst ist Klasse D die ganze Zeit. – Javier

+0

Interessant, hätte ich gedacht, wenn __mro__ beim Ausführen von dir (Klasse) auftauchte, aber es nicht tut. Aber wenn du dir (class .__ class__) machst, dann ist es sichtbar! Irgendeine Idee warum die Diskrepanz? Klasse .__ mro__ und Klasse .__ Klasse __.__ mro__ beide arbeiten –

+3

Der Grund ist, dass __mro__ von der Metaklasse definiert wird, nicht von der Klasse, daher wird sie nicht in dir angezeigt (siehe http://mail.python.org/pipermail/) python-dev/2008-März/077604.html). –

3

super() kennt die volle Klassenhierarchie.Dies ist, was in B init passiert:

>>> super(B, self) 
<super: <class 'B'>, <D object>> 

Dies löst die zentrale Frage,

wie funktioniert das Objekt durch Super in D init Definition kommunizieren, um die Existenz von C auf das Objekt zurück von Superzurück in B's Init-Definition?

Das heißt, in der B init Definition self ist eine Instanz D und kommuniziert damit die Existenz von C. Zum Beispiel C kann in type(self).__mro__ gefunden werden.

1

Jacobs Antwort zeigt, wie man das Problem versteht, während Bathrat die Details zeigt und hrr's geht direkt auf den Punkt.

Eine Sache, sie decken nicht (zumindest nicht explizit) aus Ihrer Frage ist dieser Punkt:

Wenn Sie jedoch den Aufruf von super in B init-Funktion auf Kommentar, weder A noch C der init-Funktion wird genannt.

Um zu verstehen, dass, ändern Jakobs-Code zu drucken, den Stapel auf A init, wie unten:

import traceback 

class A(object): 
    def __init__(self): 
     print "A init" 
     print self.__class__.__mro__ 
     traceback.print_stack() 

class B(A): 
    def __init__(self): 
     print "B init" 
     print self.__class__.__mro__ 
     super(B, self).__init__() 

class C(A): 
    def __init__(self): 
     print "C init" 
     print self.__class__.__mro__ 
     super(C, self).__init__() 

class D(B, C): 
    def __init__(self): 
     print "D init" 
     print self.__class__.__mro__ 
     super(D, self).__init__() 

x = D() 

Es ist ein wenig überraschend zu sehen, dass B ‚s Linie super(B, self).__init__() ruft tatsächlich C.__init__(), als C ist keine Basisklasse von B.

D init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 
B init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 
C init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 
A init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 
    File "/tmp/jacobs.py", line 31, in <module> 
    x = D() 
    File "/tmp/jacobs.py", line 29, in __init__ 
    super(D, self).__init__() 
    File "/tmp/jacobs.py", line 17, in __init__ 
    super(B, self).__init__() 
    File "/tmp/jacobs.py", line 23, in __init__ 
    super(C, self).__init__() 
    File "/tmp/jacobs.py", line 11, in __init__ 
    traceback.print_stack() 

Dies geschieht, weil super (B, self) nicht 'der B Basisklasse Version von __init__ Aufruf' ist. Stattdessen ist es 'Aufruf __init__ auf der ersten Klasse auf der rechten Seite B, die auf self' s __mro__ ist, und das hat solch ein Attribut.

Also, wenn Sie Kommentar den Aufruf von super in B init-Funktion aus, wird das Verfahren Stapel auf B.__init__ stoppen, und wird nie C oder A erreichen.

Fassen wir zusammen:

  • Unabhängig davon, welche Klasse sie bezieht, self ist immer ein Verweis auf die Instanz, und seine __mro__ und __class__ konstant bleiben
  • super() auf die das Verfahren findet suchen Klassen, die rechts von der aktuellen auf der __mro__ sind. Da die __mro__ konstant bleibt, ist es passiert, dass es als Liste durchsucht wird, nicht als Baum oder Graph.

An diesem letzten Punkt, beachten Sie, dass der vollständige Name des Algorithmus MRO ist C3 Superlinearisierungs. Das heißt, es flacht diese Struktur in eine Liste ab. Wenn die verschiedenen super() Aufrufe auftreten, werden sie effektiv diese Liste iterieren.

Verwandte Themen