2015-05-20 16 views
9

ho un i seguenti due superclassi:Python ereditarietà multipla: chiamare eccellente su tutti

class Parent1(object): 
    def on_start(self): 
     print('do something') 

class Parent2(object): 
    def on_start(self): 
     print('do something else') 

mi piacerebbe avere una classe figlia che eredita da entrambi in grado di chiamare super-per entrambi i genitori.

class Child(Parent1, Parent2): 
    def on_start(self): 
     # super call on both parents 

Qual è il modo Python per farlo? Grazie.

+2

Prova a leggere [questa domanda/risposte] (http://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance) – nnaelle

+0

possibile duplicato di [Understanding Python super() con i metodi \ _ \ _ init \ _ \ _()] (http://stackoverflow.com/questions/576169/understanding-python-super-with-init-methods) – Alexander

risposta

12

sintesi Exec:

solo Super esegue un metodo basato sulla gerarchia delle classi __mro__. Se si desidera eseguire più di un metodo con lo stesso nome, è necessario scrivere in modo cooperativo le classi genitore (chiamando super in modo implicito o esplicito) oppure è necessario eseguire il ciclo su valori __bases__ o __mro__ delle classi figlio.

Il lavoro di super è delegare parte o tutto una chiamata di metodo ad un metodo esistente nell'albero degli antenati delle classi. La delega può andare ben al di fuori delle classi che controlli. Il nome del metodo delegato deve esistere nel gruppo di classi base.

Il metodo presentato qui di seguito utilizzando __bases__ con try/except è più vicina ad una risposta completa alla tua domanda di come chiamare il metodo di ogni genitore con lo stesso nome.


super è utile nella situazione in cui si desidera chiamare uno dei metodi del genitore, ma non si sa quale dei due genitori:

class Parent1(object): 
    pass 

class Parent2(object): 
    # if Parent 2 had on_start - it would be called instead 
    # because Parent 2 is left of Parent 3 in definition of Child class 
    pass 

class Parent3(object): 
    def on_start(self): 
     print('the ONLY class that has on_start')   

class Child(Parent1, Parent2, Parent3): 
    def on_start(self): 
     super(Child, self).on_start() 

In questo caso, Child ha tre genitori immediati. Solo uno, Parent3, ha un metodo on_start. La chiamata a super risolve che solo Parent3 ha on_start e che è il metodo che viene chiamato.

Se Child eredita da più di una classe che ha un metodo on_start, l'ordine viene risolto da sinistra a destra (come elencato nella definizione della classe) e dal basso verso l'alto (come eredità logica). Viene chiamato solo uno dei metodi e gli altri metodi con lo stesso nome nella gerarchia delle classi sono stati sostituiti.

Quindi, più comunemente:

class GreatGrandParent(object): 
    pass 

class GrandParent(GreatGrandParent): 
    def on_start(self): 
     print('the ONLY class that has on_start') 

class Parent(GrandParent): 
    # if Parent had on_start, it would be used instead 
    pass   

class Child(Parent): 
    def on_start(self): 
     super(Child, self).on_start() 

Se si desidera chiamare più metodi genitori per nome del metodo, è possibile utilizzare __bases__ al posto di super in questo caso e iterare le classi base di Child senza conoscere le classi in base al nome:

class Parent1(object): 
    def on_start(self): 
     print('do something') 

class Parent2(object): 
    def on_start(self): 
     print('do something else') 

class Child(Parent1, Parent2): 
    def on_start(self): 
     for base in Child.__bases__: 
      base.on_start(self) 

>>> Child().on_start() 
do something 
do something else 

Se c'è una possibilità una delle cla di base SSES non hai on_start è possibile utilizzare try/except:

class Parent1(object): 
    def on_start(self): 
     print('do something') 

class Parent2(object): 
    def on_start(self): 
     print('do something else') 

class Parent3(object): 
    pass   

class Child(Parent1, Parent2, Parent3): 
    def on_start(self): 
     for base in Child.__bases__: 
      try: 
       base.on_start(self) 
      except AttributeError: 
       # handle that one of those does not have that method 
       print('"{}" does not have an "on_start"'.format(base.__name__)) 

>>> Child().on_start() 
do something 
do something else 
"Parent3" does not have an "on_start" 

Utilizzando __bases__ agirà simile a super ma per ogni gerarchia delle classi definite nella definizione Child. cioè, andrà anche se ogni classe forbearer fino on_start è soddisfatta volta per ciascun genitore della classe:

class GGP1(object): 
    def on_start(self): 
     print('GGP1 do something') 

class GP1(GGP1): 
    def on_start(self): 
     print('GP1 do something else') 

class Parent1(GP1): 
    pass   

class GGP2(object): 
    def on_start(self): 
     print('GGP2 do something') 

class GP2(GGP2): 
    pass 

class Parent2(GP2): 
    pass    

class Child(Parent1, Parent2): 
    def on_start(self): 
     for base in Child.__bases__: 
      try: 
       base.on_start(self) 
      except AttributeError: 
       # handle that one of those does not have that method 
       print('"{}" does not have an "on_start"'.format(base.__name__)) 

>>> Child().on_start() 
GP1 do something else 
GGP2 do something 
# Note that 'GGP1 do something' is NOT printed since on_start was satisfied by 
# a descendant class L to R, bottom to top 

Ora immaginate una più complessa struttura di ereditarietà:

enter image description here

Se si desidera il metodo on_start di ogni forbearer, è possibile utilizzare __mro__ e filtrare le classi che non hanno on_start come parte del loro __dict__ per quella classe. Altrimenti, si otterrà potenzialmente il metodo on_start di un pirata. In altre parole, hassattr(c, 'on_start') è True per ogni classe che è Child discendente (eccetto object in questo caso) poiché Ghengis ha un attributo on_start e tutte le classi sono classi discendenti di Ghengis.

** Attenzione - Demo Solo **

class Ghengis(object): 
    def on_start(self): 
     print('Khan -- father to all')  

class GGP1(Ghengis): 
    def on_start(self): 
     print('GGP1 do something') 

class GP1(GGP1): 
    pass 

class Parent1(GP1): 
    pass   

class GGP2(Ghengis): 
    pass 

class GP2(GGP2): 
    pass 

class Parent2(GP2): 
    def on_start(self): 
     print('Parent2 do something') 

class Child(Parent1, Parent2): 
    def on_start(self): 
     for c in Child.__mro__[1:]: 
      if 'on_start' in c.__dict__.keys(): 
       c.on_start(self) 

>>> Child().on_start() 
GGP1 do something 
Parent2 do something 
Khan -- father to all 

Ma questo ha anche un problema - se Child viene ulteriormente sottoclasse, allora il bambino del bambino sarà anche ciclo rispetto allo stesso __mro__ catena.

Come affermato da Raymond Hettinger:

super() è nel business del metodo di delegare chiamate a qualche classe in albero degli antenati dell'istanza. Per le chiamate al metodo riordinabili da utilizzare, , le classi devono essere progettate in modo cooperativo. Questo presenta tre facilmente risolvere questioni pratiche:

1) il metodo viene chiamato da super() deve esistere

2) il chiamante e il chiamato devono avere una firma argomento di corrispondenza e

3) ogni occorrenza del metodo deve utilizzare Super()

la soluzione è scrivere classi cooperative che utilizzano uniformemente super l'elenco antenato o uso creativo del adapter pattern di adattarsi classi non puoi controllare.Questi metodi sono discussi in modo più completo nell'articolo Python’s super() considered super! di Raymond Hettinger.

+0

Posso chiedere perché il voto negativo e come posso migliorare questa risposta? – dawg

+0

Downvoting perché questo fornisce una spiegazione fuorviante su quale metodo 'super' chiamerà e non spiega come eseguire correttamente l'ereditarietà multipla cooperativa. In particolare, se si tenta di ereditare multiple da una classe che esegue il ciclo su '__mro__', le opzioni per chiamare tutti i metodi degli antenati funzionano correttamente; in particolare, il loop su '__mro__' farà sì che alcuni metodi vengano chiamati due volte. – user2357112

+0

@ user2357112: quale parte è fuorviante su quale ordine 'super' risolve le chiamate di metodo? * L'ordine di risoluzione di super è basato sulla nozione di mro di python. * Ho fornito più collegamenti per spiegarlo ulteriormente. Se qualche altra classe di antenati si sposta su mro, come non è corretto ripetere il ciclo qui? Quale metodo viene chiamato due volte? Questo sembra un po 'aspro ... – dawg

0

Nel tuo caso, poiché entrambi i genitori implementano lo stesso metodo, lo super sarà lo stesso del primo genitore ereditato, da sinistra a destra (per il tuo codice, Parent1). Chiamare due funzioni con super è impossibile. Per fare ciò che si vuole, si deve semplicemente chiamare il metodo della classe genitore, come segue:

class Child(Parent1, Parent2): 
    def on_start (self): 
     Parent1.on_start() 
     Parent2.on_start() 
+1

Perché down-voting? Senza una spiegazione è inutile. – Spirine

+0

Sento un troll down-voting –

1
class Parent1(object): 
    def on_start(self): 
     print('do something') 

class Parent2(object): 
    def on_start(self): 
     print('do something else') 

class Child(Parent1, Parent2): 
    def on_start(self): 
     super(Child, self).on_start() 
     super(Parent1, self).on_start() 

c = Child() 

c.on_start() 
do something 
do something else 

O senza super:

class Child(Parent1, Parent2): 
    def on_start(self): 
     Parent1.on_start(self) 
     Parent2.on_start(self) 
+1

Downvote esattamente? –

+2

Mi sembra che le risposte qui siano state sistematicamente downvoted. – dawg