Domanda Comprendere Python super () con i metodi __init __ () [duplicato]


Questa domanda ha già una risposta qui:

Sto cercando di capire l'uso di super(). Dal suo aspetto, entrambe le classi figlio possono essere create, bene.

Sono curioso di sapere la differenza effettiva tra le seguenti 2 classi di bambini.

class Base(object):
    def __init__(self):
        print "Base created"

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()

ChildA() 
ChildB()

1979
2018-02-23 00:30


origine


risposte:


super() ti permette di evitare di fare riferimento alla classe base in modo esplicito, il che può essere bello. Ma il vantaggio principale arriva con l'ereditarietà multipla, in cui ogni sorta di cose divertenti può succedere. Vedere il documenti standard su super se non l'hai già fatto.

Nota che la sintassi modificata in Python 3.0: puoi solo dire super().__init__() invece di super(ChildB, self).__init__() quale IMO è un po 'più bello.


1423
2018-02-23 00:37



Sto cercando di capire super()

Il motivo per cui lo usiamo super è così che le classi figlio che possono utilizzare l'ereditarietà multipla cooperativa chiameranno la corretta funzione della classe genitore successiva nell'ordine di risoluzione dei metodi (MRO).

In Python 3, possiamo chiamarlo così:

class ChildB(Base):
    def __init__(self):
        super().__init__() 

In Python 2, è necessario utilizzarlo in questo modo:

        super(ChildB, self).__init__()

Senza super, sei limitato nella tua capacità di utilizzare l'ereditarietà multipla:

        Base.__init__(self) # Avoid this.

Spiego ulteriormente di seguito.

"Che differenza c'è in questo codice ?:"

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()
        # super().__init__() # you can call super like this in Python 3!

La principale differenza in questo codice è che si ottiene uno strato di riferimento indiretto in __init__ con super, che usa la classe corrente per determinare la classe successiva __init__ per cercare nell'MRO.

Illustro questa differenza in una risposta al domanda canonica, come usare 'super' in Python?, che dimostra iniezione di dipendenza e eredità multipla cooperativa.

Se Python non ha avuto super

Ecco il codice che è in realtà strettamente equivalente a super (come è implementato in C, meno il controllo e il comportamento di fallback e tradotto in Python):

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()             # Get the Method Resolution Order.
        check_next = mro.index(ChildB) + 1 # Start looking after *this* class.
        while check_next < len(mro):
            next_class = mro[check_next]
            if '__init__' in next_class.__dict__:
                next_class.__init__(self)
                break
            check_next += 1

Scritto un po 'di più come Python nativo:

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()
        for next_class in mro[mro.index(ChildB) + 1:]: # slice to end
            if hasattr(next_class, '__init__'):
                next_class.__init__(self)
                break

Se non avessimo il super oggetto, dovremmo scrivere questo codice manuale ovunque (o ricrearlo!) per assicurarci di chiamare il metodo successivo corretto nell'ordine di risoluzione dei metodi!

Come fa super farlo in Python 3 senza che gli venga detto esplicitamente quale classe e istanza dal metodo da cui è stata chiamata?

Ottiene il frame dello stack chiamante e trova la classe (memorizzata implicitamente come variabile libera locale, __class__, rendendo la funzione chiamante una chiusura sulla classe) e il primo argomento a quella funzione, che dovrebbe essere l'istanza o la classe che lo informa quale MRO (Method Resolution Order) utilizzare.

Dal momento che richiede quel primo argomento per la MRO, utilizzando super con metodi statici è impossibile.

Critiche di altre risposte:

super () ti permette di evitare di fare riferimento alla classe base in modo esplicito, il che può essere bello. . Ma il vantaggio principale arriva con l'ereditarietà multipla, dove possono accadere ogni genere di cose divertenti. Vedi i documenti standard su super se non lo hai già fatto.

È piuttosto mano-ondeggiante e non ci dice molto, ma il punto di super non è quello di evitare di scrivere la classe genitore. Il punto è garantire che venga chiamato il prossimo metodo in linea nell'ordine di risoluzione dei metodi (MRO). Questo diventa importante nell'ereditarietà multipla.

Spiegherò qui.

class Base(object):
    def __init__(self):
        print("Base init'ed")

class ChildA(Base):
    def __init__(self):
        print("ChildA init'ed")
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        print("ChildB init'ed")
        super(ChildB, self).__init__()

E creiamo una dipendenza che vogliamo essere chiamati dopo il Bambino:

class UserDependency(Base):
    def __init__(self):
        print("UserDependency init'ed")
        super(UserDependency, self).__init__()

Ora ricorda, ChildB usa super, ChildA non:

class UserA(ChildA, UserDependency):
    def __init__(self):
        print("UserA init'ed")
        super(UserA, self).__init__()

class UserB(ChildB, UserDependency):
    def __init__(self):
        print("UserB init'ed")
        super(UserB, self).__init__()

E UserA non chiama il metodo UserDependency:

>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>

Ma UserB, perché ChildB usi super, fa !:

>>> UserB()
UserB init'ed
ChildB init'ed
UserDependency init'ed
Base init'ed
<__main__.UserB object at 0x0000000003403438>

Critica per un'altra risposta

In nessun caso dovresti fare quanto segue, che un'altra risposta suggerisce, dato che otterrai sicuramente degli errori quando sottostivi ChildB:

        super(self.__class__, self).__init__() # Don't do this. Ever.

(Questa risposta non è intelligente o particolarmente interessante, ma nonostante le critiche dirette nei commenti e oltre 17 downvotes, il rispondente ha insistito nel suggerirlo finché un gentile editore non ha risolto il suo problema).

Spiegazione: la risposta suggeriva di chiamare super in questo modo:

super(self.__class__, self).__init__()

Questo è completamente sbagliato. super ci permette di cercare il prossimo genitore nella MRO (vedi la prima sezione di questa risposta) per le classi figlie. Se lo dici super siamo nel metodo dell'istanza del bambino, quindi cercherà il successivo metodo in linea (probabilmente questo) con conseguente ricorsione, probabilmente causando un errore logico (nell'esempio del rispondente, lo fa) o un RuntimeError quando viene superata la profondità di ricorsione.

>>> class Polygon(object):
...     def __init__(self, id):
...         self.id = id
...
>>> class Rectangle(Polygon):
...     def __init__(self, id, width, height):
...         super(self.__class__, self).__init__(id)
...         self.shape = (width, height)
...
>>> class Square(Rectangle):
...     pass
...
>>> Square('a', 10, 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'

406
2017-11-25 19:00



È stato notato che in Python 3.0+ puoi usare

super().__init__() 

per effettuare la chiamata, che è concisa e non richiede di fare riferimento esplicitamente ai nomi di classe OR padre, il che può essere utile. Voglio solo aggiungere che per Python 2.7 o meno, è possibile ottenere questo comportamento insensibile ai nomi scrivendo self.__class__ invece del nome della classe, ad es.

super(self.__class__, self).__init__()

TUTTAVIA, questo rompe le chiamate a super per tutte le classi che ereditano dalla tua classe, dove self.__class__ potrebbe restituire una classe figlio. Per esempio:

class Polygon(object):
    def __init__(self, id):
        self.id = id

class Rectangle(Polygon):
    def __init__(self, id, width, height):
        super(self.__class__, self).__init__(id)
        self.shape = (width, height)

class Square(Rectangle):
    pass

Qui ho una lezione Square, che è una sottoclasse di Rectangle. Dire che non voglio scrivere un costruttore separato per Square perché il costruttore per Rectangle è abbastanza buono, ma per qualche ragione voglio implementare un quadrato in modo da poter reimplementare qualche altro metodo.

Quando creo a Square utilizzando mSquare = Square('a', 10,10), Python chiama il costruttore Rectangle perché non ho dato Square il suo costruttore. Tuttavia, nel costruttore per Rectangle, la chiamata super(self.__class__,self) sta per restituire la superclasse di mSquare, quindi chiama il costruttore Rectangle ancora. Ecco come avviene il ciclo infinito, come menzionato da @S_C. In questo caso, quando corro super(...).__init__() Sto chiamando il costruttore Rectangle ma poiché non gli do argomenti, avrò un errore.


221
2017-10-08 20:08



Super non ha effetti collaterali

Base = ChildB

Base()

funziona come previsto

Base = ChildA

Base()

entra nella ricorsione infinita.


75
2017-11-27 23:26



Solo un testa a testa ... con Python 2.7, e credo da allora super() è stato introdotto nella versione 2.2, puoi solo chiamare super()se uno dei genitori eredita da una classe che alla fine eredita object (classi di nuovo stile).

Personalmente, come per il codice Python 2.7, continuerò a usarlo BaseClassName.__init__(self, args) fino a quando non ottengo il vantaggio di usare super().


68
2018-05-25 17:52



Non c'è, davvero. super() guarda alla prossima classe nel MRO (ordine di risoluzione del metodo, accessibile con cls.__mro__) per chiamare i metodi. Basta chiamare la base __init__ chiama la base __init__. Come succede, la MRO ha esattamente un oggetto, la base. Quindi stai davvero facendo la stessa identica cosa, ma in un modo più carino con super() (in particolare se si passa all'eredità multipla in seguito).


45
2018-02-23 00:34



La differenza principale è questa ChildA.__init__ chiamerà incondizionatamente Base.__init__ mentre ChildB.__init__ chiamerà __init__ in qualunque classe capiti di essere ChildB antenato in selfLa linea degli antenati (che può differire da quello che ti aspetti).

Se aggiungi un ClassC che utilizza l'ereditarietà multipla:

class Mixin(Base):
  def __init__(self):
    print "Mixin stuff"
    super(Mixin, self).__init__()

class ChildC(ChildB, Mixin):  # Mixin is now between ChildB and Base
  pass

ChildC()
help(ChildC) # shows that the the Method Resolution Order is ChildC->ChildB->Mixin->Base

poi Base non è più il genitore di ChildB per ChildC le istanze. Adesso super(ChildB, self) punterà a Mixin Se self è un ChildC esempio.

Hai inserito Mixin nel mezzo ChildB e Base. E puoi approfittarne con super()

Quindi, se hai progettato le tue classi in modo che possano essere utilizzate in uno scenario di Ereditarietà cooperativa, lo usi super perché non sai davvero chi sarà l'antenato in fase di runtime.

Il super considerato super post e pycon 2015 video di accompagnamento spiegalo abbastanza bene


22
2017-09-21 06:41