2009-03-03 8 views
46

Sto eseguendo Python 2.5, quindi questa domanda potrebbe non essere applicabile a Python 3. Quando si crea una gerarchia di classi diamond usando l'ereditarietà multipla e si crea un oggetto della classe derivata-più, Python fa la cosa giusta (TM). Chiama il costruttore per la classe derivata-most, quindi le sue classi genitore come elencate da sinistra a destra, quindi il nonno. Ho familiarità con Python MRO; non è una mia domanda Sono curioso di sapere come l'oggetto restituito da Super riesca effettivamente a comunicare alle chiamate di super nelle classi genitore l'ordine corretto. Si consideri questo codice di esempio:Come fa il "super" di Python a fare la cosa giusta?

#!/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() 

Il codice fa la cosa intuitiva, stampa:

D init 
B init 
C init 
A init 

Tuttavia, se si commento la chiamata a super nella funzione init di B, né A né funzione init di C è chiamato. Ciò significa che la chiamata a super di B è in qualche modo consapevole dell'esistenza di C nella gerarchia di classi generale. So che super restituisce un oggetto proxy con un operatore get sovraccarico, ma in che modo l'oggetto restituito da super nella definizione di init di D comunica l'esistenza di C all'oggetto restituito da super nella definizione di init di B? L'informazione che le successive chiamate di super uso sono memorizzate sull'oggetto stesso? Se è così, perché non è super invece self.super?

Edit: Jekke ha giustamente sottolineato che non è self.super perché super è un attributo della classe, non un'istanza della classe. Concettualmente questo ha senso, ma in pratica il super non è nemmeno un attributo della classe! Puoi testare questo nell'interprete facendo due classi A e B, dove B eredita da A e chiama dir(B). Non ha attributi super o __super__.

+0

Super non è self.super perché super è una proprietà della classe, non dell'istanza. (Non capisco il resto della domanda). –

+0

Irrilevante, forse; ma mi è stato consigliato da molte persone di non usare più Python 2.5, dato che Python 3 introduce così tante nuove funzionalità/risolve così tanti bug. – adam

+0

Modifica in "in che modo l'oggetto restituito da super nella definizione di init di D comunica l'esistenza di C all'oggetto restituito da super nella definizione di init di B"? Credo che sia quello che stai chiedendo - potrebbe avere più senso :) –

risposta

14

Ho fornito una serie di link qui sotto, che rispondono alla tua domanda in modo più dettagliato e più preciso di quanto possa mai sperare di fare. Darò comunque una risposta alla tua domanda con le mie stesse parole, per farti risparmiare tempo. Lo metto in punti -

  1. super è una funzione incorporata, non un attributo.
  2. Ogni tipo (classe) in Python ha un attributo __mro__, che memorizza l'ordine di risoluzione del metodo di quella particolare istanza.
  3. Ogni chiamata a super è di tipo super (tipo [, oggetto-o-tipo]). Supponiamo che il secondo attributo sia un oggetto per il momento.
  4. Al punto di partenza delle super chiamate, l'oggetto è del tipo della classe derivata (dire DC).
  5. super cerca i metodi che corrispondono (nel tuo caso __init__) nelle classi nell'MRO, dopo la classe specificata come primo argomento (in questo caso le classi dopo DC).
  6. Quando viene trovato il metodo di corrispondenza (ad esempio in classe BC1), viene chiamato.
    (Questo metodo dovrebbe usare super, quindi presumo che lo faccia - Vedi Python super è nifty ma non può essere usato - link sotto) Questo metodo provoca quindi una ricerca nella classe dell'oggetto 'MRO per il metodo successivo, a il diritto di BC1.
  7. Ripetere il risciacquo fino a quando tutti i metodi sono stati trovati e richiamati.

Spiegazione per il tuo esempio

MRO: D,B,C,A,object 
  1. super(D, self).__init__() si chiama. isinstance (auto, D) => True
  2. Cerca prossimo metodo nel MRO nelle classi a destra del D.

    B.__init__ trovato e chiamato

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

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

  4. Così, l'MRO è lo stesso, ma la ricerca continua alla destra di B ovvero C, A , l'oggetto viene cercato uno per uno. Viene chiamato il prossimo numero __init__ trovato.

  5. E così via e così via.

Una spiegazione di super
http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
cose da guardare per quando si utilizza Super
http://fuhm.net/super-harmful/
Pythons MRO Algoritmo:
http://www.python.org/download/releases/2.3/mro/
docs Super:
http://docs.python.org/library/functions.html
La parte inferiore di questa pagina ha una bella sezione sul super:
http://docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-5-sect-2.html

Spero che questo aiuta a chiarire in su.

+0

nella tua spiegazione, non capisco il 3 ° punto "isinstance (self, B) => False", allora perché la ricerca continua? – Alcott

6

solo indovinare:

self in tutti i quattro metodi si riferiscono allo stesso oggetto, cioè, di classe D. quindi, in B.__init__(), la chiamata a super(B,self) conosce l'intera discendenza diamanti di self e deve recuperare il metodo da 'after' B. in questo caso, è la classe C.

+0

' self' dovrebbe fare riferimento all'istanza della classe contenente, e non un'altra classe lungo la linea, no? –

+1

no, se chiami un 'self.method()' sarebbe l'implementazione più specifica, non importa quanto tu sia. questa è l'essenza del polimorfismo – Javier

34

modificare il codice per questo e penso che sarà spiegare le cose (presumibilmente super sta guardando in cui, per esempio, è B nel __mro__?):

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() 

Se si esegue vedrete :

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'>) 

anche che è la pena di verificare Python's Super is nifty, but you can't use it.

+2

proprio come ho intuito: il sé è sempre di classe D. – Javier

+0

Interessante, avrei immaginato che se __mro__ si fosse presentato mentre si faceva dir (classe), ma non lo faceva. Ma se lo fai dir (class .__ class__) allora è visibile! Qualche idea del perché la discrepanza? classe .__ mro__ e classe .__ classe __.__ mro__ funzionano entrambi –

+3

Il motivo è che __mro__ è definito dal metaclasse, non dalla classe, quindi non viene visualizzato nella directory (consultare http://mail.python.org/pipermail/ python-dev/2008-marzo/077604.html). –

3

super() conosce la gerarchia di classi piena.Questo è ciò che accade all'interno di init di B:

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

Questo risolve la questione centrale,

come fa l'oggetto restituito da super-a D's definizione init comunicare l'esistenza di C per l'oggetto restituito da super-in B's definizione di init?

Vale a dire, nella definizione di init B, self è un'istanza di D, e comunica quindi l'esistenza di C. Ad esempio, C può essere trovato in type(self).__mro__.

1

La risposta di Jacob mostra come capire il problema, mentre batbrat mostra i dettagli e hrr va dritto al punto.

Una cosa che non coprono (almeno non esplicitamente) dalla tua domanda è questo punto:

Tuttavia, se si commento la chiamata a super nella funzione init di B, né A né funzione init di C è chiamato.

per capire che, cambiare il codice di Giacobbe per stampare i fogli su init di A, come di seguito:

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() 

è un po 'sorprendente vedere che B 's linea super(B, self).__init__() è in realtà chiamando C.__init__(), come C non è una baseline di 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() 

questo accade perché non è super (B, self) 'chiamando versione baseclass del B di __init__'. Invece, è "" chiamando __init__ sulla prima classe a destra di B che è presente su self "__mro__ e che ha tale attributo.

Quindi, se si commento la chiamata a super nella funzione init di B, lo stack metodo si fermerà su B.__init__, e non raggiungerà mai C o A.

Riassumendo:

  • Indipendentemente da quale classe si riferisce ad esso, self è sempre un riferimento all'istanza, e la sua __mro__ e __class__ rimangono costanti
  • super() trova il metodo osserva al le classi che si trovano a destra di quella corrente su __mro__. Poiché __mro__ rimane costante, ciò che accade è che viene cercato come elenco, non come albero o grafico.

Su quest'ultimo punto, si noti che il nome completo dell'algoritmo di MRO è C3 superclasse linearizzazione. Cioè, appiattisce quella struttura in una lista. Quando si verificano le diverse chiamate super(), stanno efficacemente iterando quella lista.