Cosa fa 'super' in Python?


564

Qual è la differenza tra:

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

e:

class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

Ho visto superessere usato parecchio in classi con una sola eredità. Posso capire perché lo useresti in eredità multipla ma non sono chiaro quali siano i vantaggi di usarlo in questo tipo di situazione.

Risposte:


309

I vantaggi super()dell'ereditarietà singola sono minimi: per lo più, non è necessario codificare il nome della classe base in ogni metodo che utilizza i suoi metodi parent.

Tuttavia, è quasi impossibile utilizzare l'ereditarietà multipla senza super(). Questo include modi di dire comuni come mixin, interfacce, classi astratte, ecc. Questo si estende al codice che in seguito estende il tuo. Se in seguito qualcuno volesse scrivere una classe estesa Childe un mixin, il loro codice non funzionerebbe correttamente.


6
puoi fornire un esempio di ciò che intendi con "non funzionerebbe correttamente"?
Charlie Parker,

319

Qual è la differenza?

SomeBaseClass.__init__(self) 

mezzi per chiamata SomeBaseClass's __init__. mentre

super(Child, self).__init__()

significa chiamare un limite __init__dalla classe genitore che segue Childnell'ordine di risoluzione del metodo (MRO) dell'istanza.

Se l'istanza è una sottoclasse di Child, potrebbe esserci un genitore diverso che verrà dopo nell'MRO.

Spiegato semplicemente

Quando scrivi una classe, vuoi che altre classi possano usarla. super()rende più facile per le altre classi l'uso della classe che stai scrivendo.

Come dice Bob Martin, una buona architettura consente di rimandare il processo decisionale il più a lungo possibile.

super() può abilitare quel tipo di architettura.

Quando un'altra classe subclasse la classe che hai scritto, potrebbe anche ereditare da altre classi. E quelle classi potrebbero avere un risultato __init__successivo __init__basato sull'ordinamento delle classi per la risoluzione del metodo.

Senza di superte probabilmente codificheresti il ​​genitore della classe che stai scrivendo (come nell'esempio). Ciò significherebbe che non si chiamerà il prossimo __init__nell'MRO e quindi non si riutilizzerebbe il codice in esso.

Se stai scrivendo il tuo codice per uso personale, potresti non preoccuparti di questa distinzione. Ma se desideri che altri utilizzino il tuo codice, l'utilizzo superè una cosa che consente una maggiore flessibilità per gli utenti del codice.

Python 2 contro 3

Funziona in Python 2 e 3:

super(Child, self).__init__()

Funziona solo in Python 3:

super().__init__()

Funziona senza argomenti spostandosi verso l'alto nel frame dello stack e ottenendo il primo argomento per il metodo (di solito selfper un metodo di istanza o clsper un metodo di classe - ma potrebbe essere altri nomi) e trovando la classe (ad esempio Child) nelle variabili libere ( viene cercato con il nome __class__come variabile di chiusura libera nel metodo).

Preferisco dimostrare il modo di usare il cross-compatibile super, ma se stai usando solo Python 3, puoi chiamarlo senza argomenti.

Indiretto con compatibilità diretta

Cosa ti dà? Per singola eredità, gli esempi della domanda sono praticamente identici dal punto di vista dell'analisi statica. Tuttavia, l'utilizzo superoffre un livello di riferimento indiretto con compatibilità diretta.

La compatibilità diretta è molto importante per gli sviluppatori esperti. Volete che il codice continui a funzionare con modifiche minime mentre lo modificate. Quando guardi la tua cronologia delle revisioni, vuoi vedere esattamente cosa è cambiato quando.

Puoi iniziare con una singola eredità, ma se decidi di aggiungere un'altra classe di base, devi solo cambiare la linea con le basi - se le basi cambiano in una classe da cui erediti (diciamo che viene aggiunto un mixin) cambieresti niente in questa classe. Soprattutto in Python 2, ottenere supercorrettamente gli argomenti e gli argomenti del metodo corretto può essere difficile. Se sai che stai usando supercorrettamente con la singola eredità, questo rende il debug meno difficile da eseguire in futuro.

Iniezione di dipendenza

Altre persone possono usare il tuo codice e iniettare i genitori nella risoluzione del metodo:

class SomeBaseClass(object):
    def __init__(self):
        print('SomeBaseClass.__init__(self) called')

class UnsuperChild(SomeBaseClass):
    def __init__(self):
        print('UnsuperChild.__init__(self) called')
        SomeBaseClass.__init__(self)

class SuperChild(SomeBaseClass):
    def __init__(self):
        print('SuperChild.__init__(self) called')
        super(SuperChild, self).__init__()

Supponi di aggiungere un'altra classe al tuo oggetto e desideri iniettare una classe tra Foo e Bar (per test o per qualche altro motivo):

class InjectMe(SomeBaseClass):
    def __init__(self):
        print('InjectMe.__init__(self) called')
        super(InjectMe, self).__init__()

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

L'uso del bambino non super non riesce a iniettare la dipendenza perché il bambino che stai utilizzando ha codificato il metodo da chiamare dopo il suo:

>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called

Tuttavia, la classe con il figlio che utilizza superpuò iniettare correttamente la dipendenza:

>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called

Affrontare un commento

Perché nel mondo questo sarebbe utile?

Python linearizza un albero ereditario complicato tramite l' algoritmo di linearizzazione C3 per creare un Method Resolution Order (MRO).

Vogliamo che i metodi vengano cercati in questo ordine .

Affinché un metodo definito in un genitore possa trovare quello successivo in quell'ordine senza super, dovrebbe farlo

  1. ottenere il mro dal tipo di istanza
  2. cerca il tipo che definisce il metodo
  3. trova il tipo successivo con il metodo
  4. associare quel metodo e chiamarlo con gli argomenti previsti

Non UnsuperChilddovrebbe avere accesso a InjectMe. Perché la conclusione "Evita sempre di usare super" non è la conclusione ? Cosa mi sto perdendo qui?

La UnsuperChildfa non hanno accesso a InjectMe. È quello UnsuperInjectorche ha accesso a InjectMe- e tuttavia non può chiamare il metodo di quella classe dal metodo da cui eredita UnsuperChild.

Entrambe le classi Child intendono chiamare un metodo con lo stesso nome che viene dopo nell'MRO, che potrebbe essere un'altra classe di cui non era a conoscenza al momento della creazione.

Quello senza supercodificare il metodo del suo genitore - quindi ha limitato il comportamento del suo metodo e le sottoclassi non possono iniettare funzionalità nella catena di chiamate.

Quello con super ha una maggiore flessibilità. La catena di chiamata per i metodi può essere intercettata e la funzionalità iniettata.

Potresti non aver bisogno di quella funzionalità, ma potrebbero esserci delle sottoclassi del tuo codice.

Conclusione

Utilizzare sempre superper fare riferimento alla classe padre anziché codificarla.

Quello che intendi è fare riferimento alla classe genitore che è il prossimo in linea, non specificamente quella che vedi ereditare dal figlio.

Il mancato utilizzo superpuò comportare vincoli non necessari per gli utenti del codice.


In C, DI è come questo . il codice è qui . Se aggiungo un'altra implementazione listdell'interfaccia, supponiamo che doublylinkedlistl'applicazione la selezioni senza problemi. Posso rendere il mio esempio più configurabile introducendo config.txte collegando l'implementazione al momento del caricamento. È questo l'esempio giusto? Se sì, come posso collegare il tuo codice? Vedi il primo adv di DI in wiki. Dove è configurabile una nuova implementazione? nel tuo codice
Sovrascrivi il

Una nuova implementazione viene creata tramite l'ereditarietà, ad esempio, dove una delle classi "Injector" eredita dalla InjectMeclasse. I commenti non sono per discussione, tuttavia, quindi ti consiglio di discuterne ulteriormente con gli altri in chat o di porre una nuova domanda sul sito principale.
Aaron Hall

Bella risposta! ma quando si usa l'ereditarietà multipla, ci sono complicazioni con super () e __init__funzioni. soprattutto se la firma di __init__varia tra le classi nella gerarchia. Ho aggiunto una risposta che si concentra su questo aspetto
Aviad Rozenhek il

35

Avevo giocato un po 'con super(), e avevo riconosciuto che possiamo cambiare l'ordine di chiamata.

Ad esempio, abbiamo la prossima struttura gerarchica:

    A
   / \
  B   C
   \ /
    D

In questo caso MRO di D sarà (solo per Python 3):

In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

Creiamo una classe in cui super()chiama dopo l'esecuzione del metodo.

In [23]: class A(object): #  or with Python 3 can define class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          print("I'm from B")
...:          super().__init__()
...:   
...: class C(A):
...:      def __init__(self):
...:          print("I'm from C")
...:          super().__init__()
...:  
...: class D(B, C):
...:      def __init__(self):
...:          print("I'm from D")
...:          super().__init__()
...: d = D()
...:
I'm from D
I'm from B
I'm from C
I'm from A

    A
   / 
  B  C
    /
    D

Quindi possiamo vedere che l'ordine di risoluzione è lo stesso di MRO. Ma quando chiamiamo super()all'inizio del metodo:

In [21]: class A(object):  # or class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          super().__init__()  # or super(B, self).__init_()
...:          print("I'm from B")
...:   
...: class C(A):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from C")
...:  
...: class D(B, C):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from D")
...: d = D()
...: 
I'm from A
I'm from C
I'm from B
I'm from D

Abbiamo un ordine diverso, è invertito un ordine della tupla MRO.

    A
   / 
  B  C
    /
    D 

Per ulteriori letture consiglierei le seguenti risposte:

  1. Esempio di linearizzazione C3 con super (una grande gerarchia)
  2. Cambiamenti importanti nel comportamento tra classi di stile vecchie e nuove
  3. The Inside Story on New-Classes Classes

Non capisco perché l'ordine sta cambiando. La prima parte capisco che DBCA perché D è la prima classe, quindi quando si carica il sé (B, C) alla fine stampa B, C, quindi solo A poiché B (A), C (A) punta a sé per il finale parte. Se seguo questa comprensione, allora la seconda parte non dovrebbe essere come BCAD? Potresti spiegarmi un po 'per favore, per favore.
JJson,

Mio male, non ho notato che ogni istanza di ogni classe è stata avviata con super () prima. Quindi, se è così, non dovrebbe essere ABCD? In qualche modo capisco come è arrivato ACBD ma ancora non riesco a convincere e ho ancora un po 'di confusione. la mia comprensione è che, d = D () ha chiamato la Classe D (B, C) con 2 auto-parametri, poiché super () viene avviato per primo quindi B viene chiamato insieme ai suoi attributi quindi D non viene stampato prima che C sia perché Classe D (B, C) contiene 2 parametri personali quindi deve eseguire il secondo che è la Classe C (A), dopo l'esecuzione non ci sono più parametri personali da eseguire
JJson,

Si baserà sulla definizione di mro .
SKhalymon,

1
quindi stampa C quindi stampa B e infine stampa D. Ho ragione?
JJson,

2
È molto facile capire il secondo fintanto che ottieni il primo. È proprio come uno stack. spingi la stampa '' in pila e fai super (), quando ha finito la A, inizia a stampare le cose in quella pila, quindi l'ordine è inverso.
concedi domenica

35

Tutto ciò non presuppone che la classe base sia una nuova classe?

class A:
    def __init__(self):
        print("A.__init__()")

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

Non funzionerà in Python 2. class Adeve essere di nuovo stile, ovvero:class A(object)


20

Quando chiamiamo super()per risolvere la versione di un genitore di un metodo di classe, metodo di istanza o metodo statico, vogliamo passare la classe corrente il cui ambito ci troviamo come primo argomento, per indicare a quale ambito del genitore stiamo cercando di risolvere e come un secondo argomento l'oggetto di interesse per indicare a quale oggetto stiamo cercando di applicare tale ambito.

Si consideri una gerarchia di classi A, Be Cdove ogni classe è il genitore di quello seguente, e a, be crispettive istanze di ciascuno.

super(B, b) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to b, as if b was an instance of A

super(C, c) 
# resolves to the scope of C's parent i.e. B
# and applies that scope to c

super(B, c) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to c

Usando supercon un metodo statico

ad es. usando super()dall'interno del __new__()metodo

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return super(A, cls).__new__(cls, *a, **kw)

Spiegazione:

1- anche se è usuale per __new__()prendere come primo parametro un riferimento alla classe chiamata, è non è implementato in Python come classmethod, ma piuttosto uno staticmethod. Cioè, un riferimento a una classe deve essere passato esplicitamente come primo argomento quando si chiama __new__()direttamente:

# if you defined this
class A(object):
    def __new__(cls):
        pass

# calling this would raise a TypeError due to the missing argument
A.__new__()

# whereas this would be fine
A.__new__(A)

2- quando si chiama super()per accedere alla classe genitore, si passa la classe figlio Acome primo argomento, quindi si passa un riferimento all'oggetto di interesse, in questo caso è il riferimento di classe che è stato passato quando è A.__new__(cls)stato chiamato. Nella maggior parte dei casi capita anche di essere un riferimento alla classe del bambino. In alcune situazioni potrebbe non esserlo, ad esempio nel caso di eredità di più generazioni.

super(A, cls)

3- poiché come regola generale __new__()è un metodo statico, super(A, cls).__new__restituirà anche un metodo statico e in questo caso deve essere fornito esplicitamente tutti gli argomenti, incluso il riferimento all'oggetto di più interessante cls.

super(A, cls).__new__(cls, *a, **kw)

4- fare la stessa cosa senza super

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return object.__new__(cls, *a, **kw)

Usando supercon un metodo di istanza

es. usando super()dall'interno__init__()

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        super(A, self).__init__(*a, **kw)

Spiegazione:

1- __init__è un metodo di istanza, nel senso che prende come primo argomento un riferimento a un'istanza. Quando viene chiamato direttamente dall'istanza, il riferimento viene passato in modo implicito, ovvero non è necessario specificarlo:

# you try calling `__init__()` from the class without specifying an instance
# and a TypeError is raised due to the expected but missing reference
A.__init__() # TypeError ...

# you create an instance
a = A()

# you call `__init__()` from that instance and it works
a.__init__()

# you can also call `__init__()` with the class and explicitly pass the instance 
A.__init__(a)

2- quando chiamiamo super()all'interno __init__(), passiamo alla classe figlio come primo argomento e l'oggetto di interesse come secondo argomento, che in generale è un riferimento a un'istanza della classe figlio.

super(A, self)

3- La chiamata super(A, self)restituisce un proxy che risolverà l'ambito e lo applicherà selfcome se ora fosse un'istanza della classe genitore. Chiamiamo quel proxy s. Poiché __init__()è un metodo di istanza, la chiamata s.__init__(...)passerà implicitamente un riferimento selfcome primo argomento al genitore __init__().

4- per fare lo stesso senza superche sia necessario passare esplicitamente un riferimento a un'istanza alla versione parent di __init__().

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        object.__init__(self, *a, **kw)

Usando supercon un metodo di classe

class A(object):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        print "A.alternate_constructor called"
        return cls(*a, **kw)

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return super(B, cls).alternate_constructor(*a, **kw)

Spiegazione:

1- Un metodo di classe può essere chiamato direttamente dalla classe e prende come primo parametro un riferimento alla classe.

# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()

2- quando si chiama super()all'interno di un metodo di classe per risolvere la versione del suo genitore, vogliamo passare l'attuale classe figlio come primo argomento per indicare a quale ambito del genitore stiamo cercando di risolvere e l'oggetto di interesse come secondo argomento per indicare a quale oggetto vogliamo applicare tale ambito, che in generale è un riferimento alla classe figlio stessa o ad una delle sue sottoclassi.

super(B, cls_or_subcls)

3- La chiamata si super(B, cls)risolve nell'ambito Ae la applica a cls. Poiché alternate_constructor()è un metodo di classe, la chiamata super(B, cls).alternate_constructor(...)passerà implicitamente un riferimento clscome primo argomento alla Aversione dialternate_constructor()

super(B, cls).alternate_constructor()

4- per fare lo stesso senza usare super()è necessario ottenere un riferimento alla versione non associata di A.alternate_constructor()(ovvero la versione esplicita della funzione). Fare semplicemente questo non funzionerebbe:

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return A.alternate_constructor(cls, *a, **kw)

Quanto sopra non funzionerebbe perché il A.alternate_constructor()metodo prende un riferimento implicito Acome primo argomento. L' clsessere passato qui sarebbe il suo secondo argomento.

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        # first we get a reference to the unbound 
        # `A.alternate_constructor` function 
        unbound_func = A.alternate_constructor.im_func
        # now we call it and pass our own `cls` as its first argument
        return unbound_func(cls, *a, **kw)

6

Molte grandi risposte, ma per gli studenti visivi: prima di tutto esploriamo con argomenti super, e poi senza. esempio dell'albero della super eredità

Immagina che ci sia un'istanza jackcreata dalla classe Jack, che ha la catena di eredità come mostrato in verde nella figura. Calling:

super(Jack, jack).method(...)

utilizzerà il MRO (Method Resolution Order) di jack(il suo albero di ereditarietà in un certo ordine) e inizierà la ricerca da Jack. Perché si può fornire una classe genitore? Bene, se iniziamo a cercare dall'istanza jack, troverebbe il metodo dell'istanza, l'intero punto è trovare il suo metodo parent.

Se uno non fornisce argomenti a super, è come se il primo argomento passato fosse la classe di self, e il secondo argomento passato fosse self. Questi sono calcolati automaticamente per te in Python3.

Comunque diciamo che non vogliamo usare Jackil metodo, invece di passare Jack, potremmo essere passati Jenper iniziare la ricerca verso l'alto del metodo da Jen.

Cerca uno strato alla volta (larghezza non profondità), ad es. Se Adamed Sueentrambi hanno il metodo richiesto, Sueverrà trovato per primo quello da.

Se Cained Sueentrambi avessero il metodo richiesto, Cainil metodo sarebbe chiamato per primo. Ciò corrisponde in codice a:

Class Jen(Cain, Sue):

MRO è da sinistra a destra.


2

alcune grandi risposte qui, ma non affrontano come usare super()nel caso in cui diverse classi nella gerarchia abbiano firme diverse ... specialmente nel caso di__init__

per rispondere a quella parte e per essere in grado di utilizzare efficacemente super()suggerirei di leggere la mia risposta super () e di cambiare la firma dei metodi cooperativi .

ecco solo la soluzione a questo scenario:

  1. le classi di livello superiore nella gerarchia devono ereditare da una classe personalizzata come SuperObject:
  2. se le classi possono accettare argomenti diversi, passa sempre tutti gli argomenti che hai ricevuto alla super funzione come argomenti di parole chiave e accetta sempre **kwargs.
class SuperObject:        
    def __init__(self, **kwargs):
        print('SuperObject')
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

esempio di utilizzo:

class A(SuperObject):
    def __init__(self, **kwargs):
        print("A")
        super(A, self).__init__(**kwargs)

class B(SuperObject):
    def __init__(self, **kwargs):
        print("B")
        super(B, self).__init__(**kwargs)

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

E(name='python', age=28)

produzione:

E name=python age=28
C age=28
A
D name=python
B
SuperObject

0
class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

Questo è abbastanza facile da capire.

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

Ok, cosa succede adesso se lo usi super(Child,self)?

Quando viene creata un'istanza Child, il suo MRO (Method Resolution Order) è nell'ordine di (Child, SomeBaseClass, object) in base all'ereditarietà. (supponiamo che SomeBaseClass non abbia altri genitori tranne l'oggetto predefinito)

Passando Child, self, supercerca nel MRO selfdell'istanza e restituisce l'oggetto proxy accanto a Child, in questo caso è SomeBaseClass, questo oggetto invoca quindi il __init__metodo di SomeBaseClass. In altre parole, se lo è super(SomeBaseClass,self), l'oggetto proxy che superrestituisce sarebbeobject

Per l'ereditarietà multipla, l'MRO potrebbe contenere molte classi, quindi in pratica superti consente di decidere da dove iniziare la ricerca nell'MRO.


0

Considera il seguente codice:

class X():
    def __init__(self):
        print("X")

class Y(X):
    def __init__(self):
        # X.__init__(self)
        super(Y, self).__init__()
        print("Y")

class P(X):
    def __init__(self):
        super(P, self).__init__()
        print("P")

class Q(Y, P):
    def __init__(self):
        super(Q, self).__init__()
        print("Q")

Q()

Se cambi costruttore di Yto X.__init__, otterrai:

X
Y
Q

Usando super(Y, self).__init__(), otterrai:

X
P
Y
Q

E Po Qpotrebbe anche essere coinvolto da un altro file che non conosci quando scrivi Xe Y. Quindi, in sostanza, non saprai a cosa super(Child, self)farà riferimento quando stai scrivendo class Y(X), anche la firma di Y è semplice come Y(X). Ecco perché super potrebbe essere una scelta migliore.

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.