Come funziona il super () di Python con ereditarietà multipla?


888

Sono praticamente nuovo nella programmazione orientata agli oggetti di Python e ho difficoltà a comprendere la super()funzione (nuove classi di stile) soprattutto quando si tratta di ereditarietà multipla.

Ad esempio se hai qualcosa come:

class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print "that's it"

Quello che non capisco è: la Third()classe erediterà entrambi i metodi di costruzione? Se sì, quale verrà eseguito con super () e perché?

E se si desidera eseguire l'altro? So che ha qualcosa a che fare con l'ordine di risoluzione del metodo Python ( MRO ).


In effetti, l'ereditarietà multipla è l'unico caso in cui super()è utile. Non consiglierei di usarlo con le classi usando l'eredità lineare, dove è solo inutile sovraccarico.
Bachsau,

9
@Bachsau è tecnicamente corretto in quanto è un piccolo overhead ma super () è più pythonic e consente il re-factoring e le modifiche al codice nel tempo. Usa super () a meno che tu non abbia davvero bisogno di un metodo specifico di classe denominato.
Paul Whipp,

2
Un altro problema super()è che costringe ogni sottoclasse a usarlo anche, mentre quando non lo usa super(), tutti i sottoclassi possono decidere da soli. Se uno sviluppatore che lo utilizza non è a conoscenza super()o non sa che è stato utilizzato, possono sorgere problemi con il mro che sono molto difficili da rintracciare.
Bachsau,

Ho trovato praticamente ogni risposta qui confusa in un modo o nell'altro. Faresti invece riferimento qui .
matanster

Risposte:


709

Questo è dettagliato con una ragionevole quantità di dettagli dallo stesso Guido nel suo post sul metodo di risoluzione del metodo (inclusi due tentativi precedenti).

Nel tuo esempio, Third()chiamerò First.__init__. Python cerca ogni attributo nei genitori della classe in quanto sono elencati da sinistra a destra. In questo caso, stiamo cercando __init__. Quindi, se lo definisci

class Third(First, Second):
    ...

Python inizierà guardando Firste, se Firstnon ha l'attributo, allora guarderà Second.

Questa situazione diventa più complessa quando l'eredità inizia a incrociare percorsi (ad esempio se First ereditati da Second). Leggi il link sopra per maggiori dettagli, ma, in poche parole, Python proverà a mantenere l'ordine in cui ogni classe appare nell'elenco delle eredità, a partire dalla classe figlio stessa.

Quindi, ad esempio, se avessi:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

il MRO sarebbe [Fourth, Second, Third, First].

A proposito: se Python non riesce a trovare un ordine coerente di risoluzione del metodo, solleverà un'eccezione, invece di ricorrere a comportamenti che potrebbero sorprendere l'utente.

Modificato per aggiungere un esempio di MRO ambiguo:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        print "third"

ThirdMRO dovrebbe essere [First, Second]o [Second, First]? Non ci sono aspettative ovvie e Python genererà un errore:

TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution order (MRO) for bases Second, First

Modifica: vedo diverse persone che sostengono che gli esempi sopra mancano di super()chiamate, quindi lasciami spiegare: il punto degli esempi è mostrare come è costruito l'MRO. Essi sono non intendono stampare "prima \ nsecond \ terzo" o qualsiasi altra cosa. Puoi - e dovresti, naturalmente, giocare con l'esempio, aggiungere super()chiamate, vedere cosa succede e ottenere una comprensione più profonda del modello di eredità di Python. Ma il mio obiettivo qui è quello di renderlo semplice e mostrare come è costruito l'MRO. Ed è costruito come ho spiegato:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)

12
Diventa più interessante (e, probabilmente, più confuso) quando inizi a chiamare super () in Primo, Secondo e Terzo [ pastebin.com/ezTyZ5Wa ].
gatoatigrado,

52
Penso che la mancanza di super chiamate nelle prime classi sia un grosso problema con questa risposta; senza discutere su come / perché questa importante comprensione critica della domanda vada persa.
Sam Hartman,

3
Questa risposta è semplicemente sbagliata. Senza le chiamate super () nei genitori, non succederà nulla. La risposta di @ lifeless è quella corretta.
Cerin,

8
@Cerin Il punto di questo esempio è mostrare come viene costruito l'MRO. L'esempio NON intende stampare "first \ nsecond \ third" o altro. E la MRO è infatti corretto: Quarto .__ mro__ == (<class ' principale .Fourth'>, <class ' principale .Second'>, <class ' principale .Third'>, <class ' principale .Prima'>, < digitare 'oggetto'>)
rbp

2
Per quanto posso vedere, a questa risposta manca una delle domande di OP, che è "E cosa succede se si desidera eseguire l'altra?". Mi piacerebbe vedere la risposta a questa domanda. Dobbiamo solo nominare esplicitamente la classe base?
Ray,

251

Il tuo codice e le altre risposte sono tutti difettosi. Mancano le super()chiamate nelle prime due classi necessarie per il funzionamento della sottoclasse cooperativa.

Ecco una versione fissa del codice:

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

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

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print("third")

La super()chiamata trova il metodo successivo nell'MRO ad ogni passaggio, motivo per cui anche First e Second devono averlo, altrimenti l'esecuzione si interrompe alla fine di Second.__init__().

Questo è quello che ottengo:

>>> Third()
second
first
third

90
Cosa fare se queste classi necessitano di parametri diversi per inizializzarsi?
calfzhou,

2
"sottoclasse cooperativa"
Quant Metropolis

6
In questo modo verranno eseguiti i metodi init di ENTRAMBE le classi base, mentre l'esempio originale chiama solo il primo init riscontrato nell'MRO. Immagino che ciò sia implicato dal termine "sottoclasse cooperativa", ma un chiarimento sarebbe stato utile ("Esplicitare è meglio che implicito", sai;))
Quant Metropolis,

1
Sì, se stai passando parametri diversi a un metodo chiamato tramite super, tutte le implementazioni di quel metodo che salgono l'MRO verso object () devono avere firme compatibili. Ciò può essere ottenuto tramite parametri di parole chiave: accetta più parametri di quelli utilizzati dal metodo e ignora quelli aggiuntivi. È generalmente considerato brutto farlo, e per la maggior parte dei casi è meglio aggiungere nuovi metodi, ma init è (quasi?) Unico come un nome di metodo speciale ma con parametri definiti dall'utente.
senza vita

15
Il design dell'ereditarietà multipla è davvero pessimo in Python. Le classi di base hanno quasi bisogno di sapere chi lo deriverà e quante altre classi di base deriveranno e in quale ordine ... altrimenti supernon riusciranno a funzionare (a causa della mancata corrispondenza dei parametri) o non chiameranno alcune delle basi (perché non hai scritto superin una delle basi che interrompe il collegamento)!
Nawaz,

186

Volevo elaborare un po ' la risposta senza vita perché quando ho iniziato a leggere su come usare super () in una gerarchia di ereditarietà multipla in Python, non l'ho ricevuta immediatamente.

Quello che devi capire è che super(MyClass, self).__init__()fornisce il metodo successivo in __init__ base all'algoritmo MRO (Method Resolution Ordering) utilizzato nel contesto della gerarchia ereditaria completa .

Quest'ultima parte è fondamentale per capire. Consideriamo di nuovo l'esempio:

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

Secondo questo articolo sull'ordine di risoluzione del metodo di Guido van Rossum, l'ordine da risolvere __init__viene calcolato (prima di Python 2.3) usando un "attraversamento da sinistra a destra in profondità":

Third --> First --> object --> Second --> object

Dopo aver rimosso tutti i duplicati, tranne l'ultimo, otteniamo:

Third --> First --> Second --> object

Quindi, seguiamo cosa succede quando istanziamo un'istanza della Thirdclasse, ad es x = Third().

  1. Secondo MRO Third.__init__esegue.
    • stampe Third(): entering
    • quindi super(Third, self).__init__()esegue e restituisce MRO First.__init__che viene chiamato.
  2. First.__init__ viene eseguito.
    • stampe First(): entering
    • quindi super(First, self).__init__()esegue e restituisce MRO Second.__init__che viene chiamato.
  3. Second.__init__ viene eseguito.
    • stampe Second(): entering
    • quindi super(Second, self).__init__()esegue e restituisce MRO object.__init__che viene chiamato.
  4. object.__init__ esegue (nessuna dichiarazione di stampa nel codice lì)
  5. l'esecuzione torna a Second.__init__cui poi stampaSecond(): exiting
  6. l'esecuzione torna a First.__init__cui poi stampaFirst(): exiting
  7. l'esecuzione torna a Third.__init__cui poi stampaThird(): exiting

Questo spiega perché l'istanza di Third () si traduce in:

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

L'algoritmo MRO è stato migliorato da Python 2.3 in poi per funzionare bene in casi complessi, ma suppongo che l'uso della "prima traversata da sinistra a destra" + "rimuovendo i duplicati previsti per l'ultimo" continui a funzionare nella maggior parte dei casi (per favore commentare se non è così). Assicurati di leggere il post sul blog di Guido!


6
Ancora non capisco perché: Inside init di First super (First, self) .__ init __ () chiama init of Second, perché questo è ciò che l'MRO impone!
user389955

@ user389955 L'oggetto creato è di tipo Terzo che ha tutti i metodi init. Quindi, se supponi che MRO crei un elenco di tutte le funzioni di init in un ordine specifico, con ogni super chiamata, stai facendo un passo avanti fino a raggiungere la fine.
Sreekumar R,

15
Penso che il passaggio 3 abbia bisogno di ulteriori spiegazioni: se Thirdnon ereditato da Second, quindi super(First, self).__init__chiamerebbe object.__init__e dopo il ritorno, verrebbe stampato "primo". Ma perché Thirderedita da entrambi Firste Second, piuttosto che chiamare object.__init__dopo First.__init__il MRO, impone che object.__init__viene conservata solo la chiamata finale a , e le dichiarazioni di stampa in Firste Secondnon vengono raggiunte fino alla object.__init__restituzione. Poiché è Secondstato l'ultimo a chiamare object.__init__, ritorna all'interno Secondprima di rientrare First.
MountainDrew

1
È interessante notare che PyCharm sembra sapere tutto questo (i suoi suggerimenti parlano di quali parametri vanno con quali chiamate a super. Ha anche una nozione di covarianza di input, quindi riconosce List[subclass]come List[superclass]se subclasssia una sottoclasse di superclass( Listproviene dal typingmodulo di PEP 483 iirc).
Reb.Cabin

Bel post ma mi mancano informazioni riguardo agli argomenti dei costruttori, cioè cosa succede se Second e First si aspettano argomenti distinti? Il costruttore di First dovrà elaborare alcuni degli argomenti e passare il resto a Second. È giusto? Non mi sembra corretto che First debba conoscere gli argomenti richiesti per Second.
Christian K.

58

Questo è noto come Diamond Problem , la pagina ha una voce su Python, ma in breve, Python chiamerà i metodi della superclasse da sinistra a destra.


Questo non è il problema del diamante. Il problema del diamante coinvolge quattro classi e la domanda del PO coinvolge solo tre.
Ian Goodfellow,

147
objectè il quarto
GP89,

28

Questo è il modo in cui ho risolto il problema di avere l'ereditarietà multipla con diverse variabili per l'inizializzazione e di avere più MixIn con la stessa chiamata di funzione. Ho dovuto aggiungere esplicitamente variabili ai ** kwarg passati e aggiungere un'interfaccia MixIn per essere un endpoint per le super chiamate.

Ecco Auna classe base estensibile Be Csono le classi MixIn che forniscono entrambe la funzione f. Aed Bentrambi si aspettano parametri vnei loro __init__e si Caspettano w. La funzione faccetta un parametro y. Qeredita da tutte e tre le classi. MixInFè l'interfaccia mixin per Be C.


class A(object):
    def __init__(self, v, *args, **kwargs):
        print "A:init:v[{0}]".format(v)
        kwargs['v']=v
        super(A, self).__init__(*args, **kwargs)
        self.v = v


class MixInF(object):
    def __init__(self, *args, **kwargs):
        print "IObject:init"
    def f(self, y):
        print "IObject:y[{0}]".format(y)


class B(MixInF):
    def __init__(self, v, *args, **kwargs):
        print "B:init:v[{0}]".format(v)
        kwargs['v']=v
        super(B, self).__init__(*args, **kwargs)
        self.v = v
    def f(self, y):
        print "B:f:v[{0}]:y[{1}]".format(self.v, y)
        super(B, self).f(y)


class C(MixInF):
    def __init__(self, w, *args, **kwargs):
        print "C:init:w[{0}]".format(w)
        kwargs['w']=w
        super(C, self).__init__(*args, **kwargs)
        self.w = w
    def f(self, y):
        print "C:f:w[{0}]:y[{1}]".format(self.w, y)
        super(C, self).f(y)


class Q(C,B,A):
    def __init__(self, v, w):
        super(Q, self).__init__(v=v, w=w)
    def f(self, y):
        print "Q:f:y[{0}]".format(y)
        super(Q, self).f(y)

Penso che questo dovrebbe forse essere una domanda e risposta separata, dal momento che l'MRO è un argomento abbastanza grande da solo senza avere a che fare con argomenti diversi tra funzioni con ereditarietà (l'ereditarietà multipla è un caso speciale).
senza vita

8
Teoricamente si. In pratica, questo scenario è emerso ogni volta che ho incontrato l'eredità di Diamond in Python, quindi l'ho aggiunto qui. Da allora, è qui che vado ogni volta che non riesco a evitare in modo pulito l'eredità dei diamanti. Ecco alcuni link extra per il futuro: rhettinger.wordpress.com/2011/05/26/super-considered-super code.activestate.com/recipes/…
brent.payne,

Ciò che vogliamo sono programmi con nomi di parametri semanticamente significativi. Ma in questo esempio quasi tutti i parametri sono nominati in modo anonimo, il che renderà molto più difficile per il programmatore originale documentare il codice e per un altro programmatore leggerlo.
Arthur,

Una richiesta pull al repository github con nomi descrittivi sarebbe apprezzata
brent.payne,

@ brent.payne Penso che @Arthur intendesse dire che tutto il tuo approccio si basa sull'uso args/ kwargspiuttosto che sui parametri nominati.
massimo

25

Capisco che questo non risponda direttamente alla super()domanda, ma ritengo sia abbastanza rilevante da condividere.

C'è anche un modo per chiamare direttamente ogni classe ereditata:


class First(object):
    def __init__(self):
        print '1'

class Second(object):
    def __init__(self):
        print '2'

class Third(First, Second):
    def __init__(self):
        Second.__init__(self)

Nota solo che se si fa in questo modo, dovrete chiamare ogni manualmente come sono abbastanza sicuro che First's __init__()non sarà chiamato.


5
Non verrà chiamato perché non hai chiamato ciascuna classe ereditata. Il problema è piuttosto che se Firste Secondentrambi ereditano un'altra classe e la chiamano direttamente, allora questa classe comune (punto di partenza del diamante) viene chiamata due volte. super lo sta evitando.
Trilarion

@Trilarion Sì, ero fiducioso che non lo fosse. Tuttavia, non lo sapevo in modo definitivo e non volevo dichiararlo come se lo avessi fatto anche se era molto improbabile. Questo è un aspetto positivo objectdell'essere chiamato due volte. Non ci ho pensato. Volevo solo sottolineare che chiamate direttamente le classi genitore.
Seaux,

Sfortunatamente, questo si interrompe se init tenta di accedere a qualsiasi metodo privato :(
Erik Aronesty

21

Complessivamente

Supponendo che tutto discenda da object(sei tu stesso se non lo fa), Python calcola un ordine di risoluzione del metodo (MRO) basato sull'albero ereditario della classe. L'MRO soddisfa 3 proprietà:

  • I bambini di una classe vengono prima dei loro genitori
  • I genitori di sinistra vengono prima dei genitori di destra
  • Una classe appare solo una volta nell'MRO

Se non esiste tale ordinamento, errori Python. Il funzionamento interno di questo è una Linerizzazione C3 delle classi ancestrali. Leggi tutto qui: https://www.python.org/download/releases/2.3/mro/

Pertanto, in entrambi gli esempi seguenti, è:

  1. Bambino
  2. Sinistra
  3. Giusto
  4. Genitore

Quando viene chiamato un metodo, la prima occorrenza di quel metodo nell'MRO è quella chiamata. Qualsiasi classe che non implementa quel metodo viene ignorata. Qualsiasi chiamata superall'interno di quel metodo chiamerà la ricorrenza successiva di quel metodo nell'MRO. Di conseguenza, importa sia l'ordine in cui metti le classi in eredità, sia il luogo in cui effettui le chiamatesuper nei metodi.

Con il superprimo in ogni metodo

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

class Left(Parent):
    def __init__(self):
        super(Left, self).__init__()
        print "left"

class Right(Parent):
    def __init__(self):
        super(Right, self).__init__()
        print "right"

class Child(Left, Right):
    def __init__(self):
        super(Child, self).__init__()
        print "child"

Child() Uscite:

parent
right
left
child

Con l' superultimo in ogni metodo

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

class Left(Parent):
    def __init__(self):
        print "left"
        super(Left, self).__init__()

class Right(Parent):
    def __init__(self):
        print "right"
        super(Right, self).__init__()

class Child(Left, Right):
    def __init__(self):
        print "child"
        super(Child, self).__init__()

Child() Uscite:

child
left
right
parent

Vedo che è possibile accedere Leftutilizzando super()da Child. supponiamo che io voglia accedere Rightdall'interno Child. C'è un modo per l'accesso Rightda Childutilizzare super-? O dovrei chiamare direttamente Rightdall'interno super?
alpha_989,

4
@ alpha_989 Se si desidera accedere solo al metodo di una determinata classe, è necessario fare riferimento direttamente a tale classe anziché utilizzare super. Super significa seguire la catena dell'eredità, non arrivare al metodo di una classe specifica.
Zags,

1
Grazie per aver menzionato esplicitamente "Una classe appare solo una volta nell'MRO". Questo ha risolto il mio problema. Ora finalmente capisco come funziona l'ereditarietà multipla. Qualcuno doveva menzionare le proprietà di MRO!
Tushar Vazirani,

18

Chi @ commento di calfzhou , è possibile utilizzare, come di solito, **kwargs:

Esempio di corsa online

class A(object):
  def __init__(self, a, *args, **kwargs):
    print("A", a)

class B(A):
  def __init__(self, b, *args, **kwargs):
    super(B, self).__init__(*args, **kwargs)
    print("B", b)

class A1(A):
  def __init__(self, a1, *args, **kwargs):
    super(A1, self).__init__(*args, **kwargs)
    print("A1", a1)

class B1(A1, B):
  def __init__(self, b1, *args, **kwargs):
    super(B1, self).__init__(*args, **kwargs)
    print("B1", b1)


B1(a1=6, b1=5, b="hello", a=None)

Risultato:

A None
B hello
A1 6
B1 5

Puoi anche usarli in posizione:

B1(5, 6, b="hello", a=None)

ma devi ricordare l'MRO, è davvero confuso.

Posso essere un po 'fastidioso, ma ho notato che le persone si sono dimenticate ogni volta di usare *argse **kwargsquando hanno la precedenza su un metodo, mentre è uno dei pochi usi davvero utili e sani di queste "variabili magiche".


Wow, è davvero brutto. È un peccato che non si possa semplicemente dire quale specifica superclasse si desidera chiamare. Tuttavia, questo mi dà ancora più incentivo a usare la composizione ed evitare l'eredità multipla come la peste.
Tom Busby,

15

Un altro punto non ancora trattato è il passaggio dei parametri per l'inizializzazione delle classi. Dalla destinazione disuper dipende dalla sottoclasse, l'unico buon modo per passare i parametri è comprimerli tutti insieme. Quindi fai attenzione a non avere lo stesso nome di parametro con significati diversi.

Esempio:

class A(object):
    def __init__(self, **kwargs):
        print('A.__init__')
        super().__init__()

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

dà:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

Chiamare la superclasse __init__direttamente ad un'assegnazione più diretta dei parametri è allettante ma fallisce se ce n'èsuper chiamata in una superclasse e / o l'MRO viene modificato e la classe A può essere chiamata più volte, a seconda dell'implementazione.

Per concludere: l'ereditarietà cooperativa e i parametri super e specifici per l'inizializzazione non funzionano molto bene insieme.


5
class First(object):
  def __init__(self, a):
    print "first", a
    super(First, self).__init__(20)

class Second(object):
  def __init__(self, a):
    print "second", a
    super(Second, self).__init__()

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__(10)
    print "that's it"

t = Third()

L'output è

first 10
second 20
that's it

Call to Third () individua l' init definito in Third. E chiama super in quella routine che richiama init definito in First. MRO = [Primo, Secondo]. Ora la chiamata a super in init definita in First continuerà a cercare MRO e troverà init definita in Second e qualsiasi chiamata a super colpirà l'oggetto predefinito init . Spero che questo esempio chiarisca il concetto.

Se non chiami super da First. La catena si interrompe e otterrai il seguente output.

first 10
that's it

1
questo perché in classe First, hai chiamato prima 'print' e poi 'super'.
Rocky Qi,

2
che doveva illustrare l'ordine di chiamata
Seraj Ahmad,

4

In learningpythonthehardway imparo qualcosa chiamato super () una funzione integrata se non sbagliata. Chiamare la funzione super () può aiutare l'eredità a passare attraverso il genitore e i 'fratelli' e aiutarti a vedere più chiaramente. Sono ancora un principiante ma adoro condividere la mia esperienza sull'uso di questo super () in python2.7.

Se hai letto i commenti in questa pagina, sentirai parlare di Method Resolution Order (MRO), poiché il metodo è la funzione che hai scritto, MRO utilizzerà lo schema Depth-First-Left-to-Right per cercare ed eseguire. Puoi fare ulteriori ricerche su questo.

Aggiungendo la funzione super ()

super(First, self).__init__() #example for class First.

Puoi connettere più istanze e 'famiglie' con super (), aggiungendo in ognuno di essi. Ed eseguirà i metodi, esaminandoli e assicurandoti di non aver perso! Tuttavia, aggiungerli prima o dopo fa la differenza, saprai se hai fatto l'apprendimento per l'esercizio 44. Lascia che il divertimento abbia inizio !!

Prendendo l'esempio di seguito, è possibile copiare e incollare e provare a eseguirlo:

class First(object):
    def __init__(self):

        print("first")

class Second(First):
    def __init__(self):
        print("second (before)")
        super(Second, self).__init__()
        print("second (after)")

class Third(First):
    def __init__(self):
        print("third (before)")
        super(Third, self).__init__()
        print("third (after)")


class Fourth(First):
    def __init__(self):
        print("fourth (before)")
        super(Fourth, self).__init__()
        print("fourth (after)")


class Fifth(Second, Third, Fourth):
    def __init__(self):
        print("fifth (before)")
        super(Fifth, self).__init__()
        print("fifth (after)")

Fifth()

Come funziona? L'istanza di fifth () andrà così. Ogni passaggio passa da una classe all'altra in cui è stata aggiunta la super funzione.

1.) print("fifth (before)")
2.) super()>[Second, Third, Fourth] (Left to right)
3.) print("second (before)")
4.) super()> First (First is the Parent which inherit from object)

Il genitore è stato trovato e andrà avanti al terzo e quarto !!

5.) print("third (before)")
6.) super()> First (Parent class)
7.) print ("Fourth (before)")
8.) super()> First (Parent class)

Ora è stato effettuato l'accesso a tutte le classi con super ()! La classe genitore è stata trovata ed eseguita e ora continua a decomprimere la funzione nelle eredità per terminare i codici.

9.) print("first") (Parent)
10.) print ("Fourth (after)") (Class Fourth un-box)
11.) print("third (after)") (Class Third un-box)
12.) print("second (after)") (Class Second un-box)
13.) print("fifth (after)") (Class Fifth un-box)
14.) Fifth() executed

Il risultato del programma sopra:

fifth (before)
second (before
third (before)
fourth (before)
first
fourth (after)
third (after)
second (after)
fifth (after)

Per me aggiungendo super () mi permette di vedere più chiaramente come Python eseguirà la mia codifica e assicurarmi che l'eredità possa accedere al metodo che intendevo.


Grazie per la demo dettagliata!
Tushar Vazirani,

3

Vorrei aggiungere ciò che @Visionscaper dice in alto:

Third --> First --> object --> Second --> object

In questo caso, l'interprete non filtra la classe oggetto perché è duplicata, piuttosto perché Second appare in una posizione di testa e non appare nella posizione di coda in un sottoinsieme di gerarchia. Mentre l'oggetto appare solo in posizioni di coda e non è considerato una posizione forte nell'algoritmo C3 per determinare la priorità.

La linearizzazione (mro) di una classe C, L (C), è la

  • la classe C
  • più l'unione di
    • linearizzazione dei suoi genitori P1, P2, .. = L (P1, P2, ...) e
    • l'elenco dei suoi genitori P1, P2, ..

L'unione linearizzata viene effettuata selezionando le classi comuni che appaiono come head list e non come coda poiché l'ordine conta (diventerà chiaro di seguito)

La linearizzazione di Third può essere calcolata come segue:

    L(O)  := [O]  // the linearization(mro) of O(object), because O has no parents

    L(First)  :=  [First] + merge(L(O), [O])
               =  [First] + merge([O], [O])
               =  [First, O]

    // Similarly, 
    L(Second)  := [Second, O]

    L(Third)   := [Third] + merge(L(First), L(Second), [First, Second])
                = [Third] + merge([First, O], [Second, O], [First, Second])
// class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists
// class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, 
                = [Third, First] + merge([O], [Second, O], [Second])
// class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3
                = [Third, First, Second] + merge([O], [O])            
                = [Third, First, Second, O]

Pertanto, per un'implementazione super () nel seguente codice:

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

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

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__()
    print "that's it"

diventa ovvio come questo metodo verrà risolto

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that's it"

"piuttosto è perché Second appare in una posizione di testa e non appare nella posizione di coda in un sottoinsieme gerarchico." Non è chiaro quale sia la posizione della testa o della coda, né quale sia un sottoinsieme di gerarchia o a quale sottoinsieme si stia riferendo.
OrangeSherbet,

La posizione della coda si riferisce alle classi più alte nella gerarchia delle classi e viceversa. L '"oggetto" della classe base si trova alla fine della coda. La chiave per comprendere l'algoritmo mro è come "Second" appare come il super di "First". Normalmente supponiamo che sia la classe "oggetto". È vero, ma solo nella prospettiva della classe "Prima". Tuttavia, se visto dal punto di vista della classe "Terza", l'ordine gerarchico per "Primo" è diverso e viene calcolato come mostrato sopra. l'algoritmo mro tenta di creare questa prospettiva (o sottoinsieme di gerarchia) per tutte le classi ereditate multiple
supi,

3

In Python 3.5+ l'eredità sembra prevedibile e molto piacevole per me. Si prega di guardare questo codice:

class Base(object):
  def foo(self):
    print("    Base(): entering")
    print("    Base(): exiting")


class First(Base):
  def foo(self):
    print("   First(): entering Will call Second now")
    super().foo()
    print("   First(): exiting")


class Second(Base):
  def foo(self):
    print("  Second(): entering")
    super().foo()
    print("  Second(): exiting")


class Third(First, Second):
  def foo(self):
    print(" Third(): entering")
    super().foo()
    print(" Third(): exiting")


class Fourth(Third):
  def foo(self):
    print("Fourth(): entering")
    super().foo()
    print("Fourth(): exiting")

Fourth().foo()
print(Fourth.__mro__)

Uscite:

Fourth(): entering
 Third(): entering
   First(): entering Will call Second now
  Second(): entering
    Base(): entering
    Base(): exiting
  Second(): exiting
   First(): exiting
 Third(): exiting
Fourth(): exiting
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

Come puoi vedere, chiama foo esattamente UNA volta per ogni catena ereditata nello stesso ordine in cui è stata ereditata. Puoi ottenere quell'ordine chiamando . mro :

Quarto -> Terzo -> Primo -> Secondo -> Base -> oggetto


2

Forse c'è ancora qualcosa che può essere aggiunto, un piccolo esempio con Django rest_framework e decoratori. Ciò fornisce una risposta alla domanda implicita: "perché dovrei volerlo comunque?"

Come detto: stiamo con Django rest_framework e stiamo usando viste generiche e per ogni tipo di oggetto nel nostro database ci troviamo con una classe di vista che fornisce GET e POST per elenchi di oggetti e un'altra classe di vista che fornisce GET , PUT e DELETE per singoli oggetti.

Ora POST, PUT e DELETE vogliamo decorare con login_required di Django. Nota come questo tocca entrambe le classi, ma non tutti i metodi in entrambe le classi.

Una soluzione potrebbe passare attraverso l'ereditarietà multipla.

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required

class LoginToPost:
    @method_decorator(login_required)
    def post(self, arg, *args, **kwargs):
        super().post(arg, *args, **kwargs)

Allo stesso modo per gli altri metodi.

Nell'elenco delle eredità delle mie classi concrete, aggiungerei il mio LoginToPostprima ListCreateAPIViewe LoginToPutOrDeleteprima RetrieveUpdateDestroyAPIView. Le mie lezioni concrete non getsarebbero state decorate.


1

Pubblicando questa risposta per il mio futuro riferimento.

L'ereditarietà multipla di Python dovrebbe usare un modello a diamante e la firma della funzione non dovrebbe cambiare nel modello.

    A
   / \
  B   C
   \ /
    D

Lo snippet di codice di esempio sarebbe: -

class A:
    def __init__(self, name=None):
        #  this is the head of the diamond, no need to call super() here
        self.name = name

class B(A):
    def __init__(self, param1='hello', **kwargs):
        super().__init__(**kwargs)
        self.param1 = param1

class C(A):
    def __init__(self, param2='bye', **kwargs):
        super().__init__(**kwargs)
        self.param2 = param2

class D(B, C):
    def __init__(self, works='fine', **kwargs):
        super().__init__(**kwargs)
        print(f"{works=}, {self.param1=}, {self.param2=}, {self.name=}")

d = D(name='Testing')

Qui la classe A è object


1
Adovrebbe anche chiamare __init__. Anon ha "inventato" il metodo __init__, quindi non può presumere che qualche altra classe possa avere Aprima nel suo MRO. L'unica classe il cui __init__metodo non chiama (e non dovrebbe) chiamare super().__init__è object.
Chepner

Si. Ecco perché ho scritto A è objectForse penso che dovrei scrivere class A (object) : invece
Akhil Nadh PC

Anon può essere objectse si aggiunge un parametro al suo __init__.
Chepner,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.