Vraag Python super () begrijpen met __init __ () methoden [dupliceren]


Deze vraag heeft hier al een antwoord:

Ik probeer het gebruik van te begrijpen super(). Van het uiterlijk, beide klassen kunnen worden gemaakt, prima.

Ik ben benieuwd naar het werkelijke verschil tussen de volgende 2 kinderlessen.

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


oorsprong


antwoorden:


super() laat je vermijden om expliciet naar de basisklasse te verwijzen, wat leuk kan zijn. Maar het belangrijkste voordeel komt met meerdere overerving, waar allerlei soorten leuke dingen kan gebeuren. Zie de standaarddocumenten op super als je dat nog niet hebt gedaan.

Let daar op de syntaxis is gewijzigd in Python 3.0: je kunt gewoon zeggen super().__init__() in plaats van super(ChildB, self).__init__() welke IMO is best een beetje aardiger.


1423
2018-02-23 00:37



ik probeer het te begrijpen super()

De reden die we gebruiken super is zo dat kindklassen die coöperatieve multiple-overerving gebruiken, de correcte volgende ouderklassenfunctie in de Method Resolution Order (MRO) noemen.

In Python 3 kunnen we het zo noemen:

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

In Python 2 zijn we verplicht om het op deze manier te gebruiken:

        super(ChildB, self).__init__()

Zonder super, bent u beperkt in uw vermogen om meerdere overerving te gebruiken:

        Base.__init__(self) # Avoid this.

Ik leg het hieronder verder uit.

"Welk verschil zit er eigenlijk in deze code ?:"

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!

Het belangrijkste verschil in deze code is dat je een laag indirecte richting krijgt in de __init__ met super, die de huidige klasse gebruikt om de volgende klassen te bepalen __init__ om op te zoeken in de MRO.

Ik illustreer dit verschil in een antwoord op de canonieke vraag, Hoe gebruik je 'super' in Python?, wat demonstreert afhankelijkheid injectie en coöperatieve meerdere overerving.

Als Python dat niet had super

Hier is de code die eigenlijk dicht op elkaar lijkt super (hoe het is geïmplementeerd in C, minus wat controle- en terugvalgedrag, en vertaald naar 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

Geschreven als een beetje meer native Python:

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

Als we de super object, dan zouden we deze handmatige code overal moeten schrijven (of opnieuw maken!) om ervoor te zorgen dat we de juiste volgende methode in de methodesolutieregel noemen!

Hoe doet super dit in Python 3 zonder expliciet te vertellen welke klasse en instantie van de methode waar het vandaan kwam?

Het krijgt het calling stack frame en vindt de klasse (impliciet opgeslagen als een lokale vrije variabele, __class__, waardoor de aanroepfunctie een afsluiting van de klasse wordt) en het eerste argument voor die functie, die de instance of klasse moet zijn die informeert welke Method Resolution Order (MRO) moet worden gebruikt.

Omdat het dat eerste argument voor de MRO vereist, gebruik makend van super met statische methoden is onmogelijk.

Kritieken van andere antwoorden:

Met super () kun je voorkomen dat je expliciet naar de basisklasse verwijst, wat leuk kan zijn. . Maar het grote voordeel is dat er meerdere overervingen plaatsvinden, waar allerlei leuke dingen kunnen gebeuren. Zie de standaarddocumenten op super als u dat nog niet heeft gedaan.

Het is nogal hand-wavey en vertelt ons niet veel, maar het punt van super is niet te voorkomen dat de ouderklasse wordt geschreven. Het punt is om ervoor te zorgen dat de volgende methode in lijn in de methode resolutie volgorde (MRO) wordt aangeroepen. Dit wordt belangrijk in meerdere overerving.

Ik zal het hier uitleggen.

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__()

En laten we een afhankelijkheid maken die we na het kind willen hebben:

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

Onthoud nu, ChildB maakt gebruik van super, ChildA doet niet:

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__()

En UserA noemt de methode UserDependency niet:

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

Maar UserB, omdat ChildB toepassingen super, doet!:

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

Kritiek voor nog een antwoord

In geen geval mag u het volgende doen, wat een ander antwoord suggereert, aangezien u zeker fouten krijgt wanneer u subklasse ChildB:

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

(Dat antwoord is niet slim of bijzonder interessant, maar ondanks directe kritiek in de commentaren en meer dan 17 downvotes, bleef de beantwoorder volhouden om het te suggereren tot een vriendelijke redacteur zijn probleem oploste.)

Uitleg: Dat antwoord suggereerde super zo te bellen:

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

Dit is helemaal fout. super laat ons de volgende ouder in de MRO opzoeken (zie het eerste deel van dit antwoord) voor kinderlessen. Als je het vertelt super we zijn in de methode van het kindinstantie, het zal dan de volgende methode in de rij opzoeken (waarschijnlijk deze) resulterend in recursie, waarschijnlijk veroorzaakt een logische fout (in het voorbeeld van de answerer doet het dat) of een RuntimeError wanneer de recursiediepte wordt overschreden.

>>> 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'

407
2017-11-25 19:00



Er is opgemerkt dat je in Python 3.0+ kunt gebruiken

super().__init__() 

om uw oproep te doen, die beknopt is en niet vereist dat u expliciet naar de naam van de ouder OF-klasse verwijst, wat handig kan zijn. Ik wil alleen toevoegen dat het voor Python 2.7 of lager mogelijk is om dit naamongevoelige gedrag te krijgen door te schrijven self.__class__ in plaats van de klassenaam, d.w.z.

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

Dit verbreekt echter oproepen naar super voor alle klassen die erft van uw klas, waar self.__class__ kon een kinderklas terugbrengen. Bijvoorbeeld:

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

Hier heb ik een klas Square, dat is een subklasse van Rectangle. Stel dat ik geen aparte constructor voor wil schrijven Square omdat de constructeur voor Rectangle is goed genoeg, maar om welke reden dan ook wil ik een Vierkant implementeren zodat ik een andere methode opnieuw kan toepassen.

Wanneer ik een maak Square gebruik makend van mSquare = Square('a', 10,10), Python roept de constructor voor Rectangle omdat ik niet heb gegeven Square zijn eigen constructeur. Echter, in de constructor voor Rectangle, de oproep super(self.__class__,self) gaat de superklasse van retourneren mSquare, dus het roept de constructor voor Rectangle nog een keer. Dit is hoe de oneindige lus gebeurt, zoals werd vermeld door @S_C. In dit geval, als ik ren super(...).__init__() Ik bel de constructeur voor Rectangle maar omdat ik het geen argumenten geef, krijg ik een foutmelding.


221
2017-10-08 20:08



Super heeft geen bijwerkingen

Base = ChildB

Base()

werkt zoals verwacht

Base = ChildA

Base()

krijgt een oneindige recursie.


75
2017-11-27 23:26



Gewoon een heads-up ... met Python 2.7, en dat geloof ik sindsdien super() werd geïntroduceerd in versie 2.2, je kunt alleen bellen super()als een van de ouders erven van een klasse die uiteindelijk erft object (klassen in nieuwe stijl).

Persoonlijk, wat betreft python 2.7-code, ga ik door met gebruiken BaseClassName.__init__(self, args) totdat ik daadwerkelijk het voordeel van het gebruik heb super().


68
2018-05-25 17:52



Dat is er niet echt. super() kijkt naar de volgende klasse in de MRO (methodeomrekeningsorder, toegankelijk met cls.__mro__) om de methoden te noemen. Gewoon de basis bellen __init__ roept de basis __init__. Het komt namelijk voor dat de MRO precies één item heeft - de basis. Dus je doet echt exact hetzelfde, maar op een leukere manier met super() (met name als je later in meerdere overerving komt).


45
2018-02-23 00:34



Het grootste verschil is dat ChildA.__init__ zal onvoorwaardelijk bellen Base.__init__ terwijl ChildB.__init__ zal bellen __init__ in welke klasse ook is ChildB voorouder in selfde lijn van voorouders (die kan verschillen van wat u verwacht).

Als u een toevoegt ClassC die meerdere overerving gebruikt:

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

dan Base is niet langer de ouder van ChildB voor ChildC instances. Nu super(ChildB, self) zal wijzen Mixin als self is een ChildC aanleg.

U hebt ingevoegd Mixin tussenin ChildB en Base. En daar kun je mee profiteren super()

Dus als u uw klassen zo hebt ontworpen dat ze kunnen worden gebruikt in een coöperatief scenario Multiple Inheritance dat u gebruikt super omdat je niet echt weet wie de voorouder zal zijn tijdens runtime.

De super overwogen superpositie en pycon 2015 bijbehorende video leg dit vrij goed uit.


22
2017-09-21 06:41