2016-02-25 5 views
11

Ho letto la programmazione Python esperta che ha un esempio di ereditarietà multipla. L'autore del libro ha spiegato ma non l'ho capito, quindi mi piacerebbe avere un'altra visione.Un oggetto viene creato due volte in Python

L'esempio mostra che l'oggetto B viene creato due volte!

Potrebbe per favore darmi una spiegazione intuitiva.

In [1]: class A(object): 
    ...:  def __init__(self): 
    ...:   print "A" 
    ...:   super(A, self).__init__() 

In [2]: class B(object): 
    ...:  def __init__(self): 
    ...:   print "B" 
    ...:   super(B, self).__init__() 

In [3]: class C(A,B): 
    ...:  def __init__(self): 
    ...:   print "C" 
    ...:   A.__init__(self) 
    ...:   B.__init__(self) 

In [4]: print "MRO:", [x.__name__ for x in C.__mro__] 
MRO: ['C', 'A', 'B', 'object'] 

In [5]: C() 
C 
A 
B 
B 
Out[5]: <__main__.C at 0x3efceb8> 

L'autore libro diceva:

Questo accade a causa della A.__init__(self) chiamata, che è fatta con l'istanza C, rendendo così super(A, self).__init__() chiamata B s' costruttore

L' punto da cui non ho avuto la sua idea è come la chiamata A.__init__(self) farà super(A, self).__init__() chiamare il costruttore B

+0

Perché non utilizza 'super 'nel metodo init di C? –

+0

Voglio solo capire come viene creato un oggetto in Python. – Bryan

+3

Se pensate che la chiamata 'super' come' chiama il prossimo metodo nell'MRO ', piuttosto che' chiama il metodo della mia classe genitore ', allora questo comportamento dovrebbe avere più senso. In questo caso, il motivo per cui vedi B stampato due volte è perché super già ordina che l'init di B sia chiamato (da init di A), e quindi quando chiami esplicitamente B.init da C, ottieni una seconda chiamata. –

risposta

8

Il super() significa semplicemente "next in line", dove la linea è mro['C', 'A', 'B', 'object']. Quindi il prossimo in linea per A è B.

Il mro viene calcolato secondo un algoritmo chiamato C3 linearization. Quando si utilizza super(), Python segue semplicemente questo ordine. Quando scrivi la tua lezione A non sai ancora quale classe sarà la prossima in linea. Solo dopo aver creato la tua classe C con ereditarietà multipla ed eseguito il programma, riceverai il mro e "sai" quale sarà il prossimo A.

Per esempio significa:

C() chiama il __init__() di C, in cui invita la __init__() di A. Ora, A utilizza super() e trova B nel mro, quindi chiama lo __init__() di B. Successivamente, lo __init__() di chiama di nuovo __init__() di B.

Calling super() nel __init__() crea un MRO diversa ed evita la doppia chiamata al __init__() di B.

from __future__ import print_function 

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

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

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

Usa:

>>> C.mro() 
[__main__.C, __main__.A, __main__.B, object] 
>> C() 
C 
A 
B 
+1

Ancora non capisco, puoi spiegarlo di più – danidee

+0

L'MRO di C è '['C', 'A', 'B', 'oggetto']'. C chiama 'A .__ init __()', A chiama 'super()', in MRO di C (dove è stato chiamato 'A .__ init __()' il successivo nella riga è 'B', quindi 'print' B'' è chiamato in A's super() e in 'B .__ init __()' –

1

Andiamo a modificare il codice un po 'e sostituire con __init__doit solo per assicurarsi che il comportamento è generico e non legati alla __init__.

Facciamo anche aggiungere più uscita per vedere cosa succede esattamente:

class A(object): 
    def doit(self): 
     print "A", self, super(A, self) 
     super(A, self).doit() 

class B(object): 
    def doit(self): 
     print "B", self, super(B, self) 

class C(A,B): 
    def doit(self): 
     print "C", self 
     A.doit(self) 
     B.doit(self) 

print "MRO:", [x.__name__ for x in C.__mro__] 
#MRO: ['C', 'A', 'B', 'object'] 

C().doit() 

Questo stamperà:

C <__main__.C object at ...> 
A <__main__.C object at ...> <super: <class 'A'>, <C object>> 
B <__main__.C object at ...> <super: <class 'B'>, <C object>> 
B <__main__.C object at ...> <super: <class 'B'>, <C object>> 

Vedete, che self è in realtà C oggetto ovunque, in modo che quando si colpisce il A.doit , in realtà hai <super: <class 'A'>, <C object>>.

che si traduce in:

per l'oggetto C chiamata al metodo del prossimo (super) doit classe dopo la A dalla lista MRO

E la prossima classe MRO dopo A è B, quindi finiamo per chiamare B.doit().

controllare anche questo codice:

class C(A,B): 

    def doit_explain(self): 
     print "C", self 
     # calls B.doit() 
     super(A, self).doit() 
     print "Back to C" 
     # calls A.doit() (and super in A also calls B.doit()) 
     super(C, self).doit() 
     print "Back to C" 
     # and just B.doit() 
     B.doit(self) 

qui invece di A.doit(self) io uso super(A, self).doit() direttamente e risulta anche in B.doit() chiamata, ecco l'output:

C <__main__.C object at ...> 
B <__main__.C object at ...> <super: <class 'B'>, <C object>> 
Back to C 
A <__main__.C object at ...> <super: <class 'A'>, <C object>> 
B <__main__.C object at ...> <super: <class 'B'>, <C object>> 
Back to C 
B <__main__.C object at ...> <super: <class 'B'>, <C object>>