Question Comprendre Python super () avec les méthodes __init __ () [dupliquer]


Cette question a déjà une réponse ici:

J'essaie de comprendre l'utilisation de super(). D'après les apparences, les deux classes enfants peuvent être créées, très bien.

Je suis curieux de connaître la différence entre les deux classes d'enfants suivantes.

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


Réponses:


super() vous évite de vous référer explicitement à la classe de base, ce qui peut être sympa. Mais le principal avantage vient avec l'héritage multiple, où toutes sortes de truc amusant ça peut arriver. Voir le docs standard sur super si tu ne l'as pas déjà fait.

Notez que la syntaxe a changé dans Python 3.0: vous pouvez juste dire super().__init__() au lieu de super(ChildB, self).__init__() quel IMO est un peu plus gentil.


1423
2018-02-23 00:37



j'essaie de comprendre super()

La raison pour laquelle nous utilisons super est tel que les classes enfants qui peuvent utiliser l'héritage multiple coopératif appellent la fonction de classe parent suivante correcte dans l'ordre de résolution de la méthode (MRO).

En Python 3, on peut l'appeler comme ceci:

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

En Python 2, nous devons l'utiliser comme ceci:

        super(ChildB, self).__init__()

Sans super, vous êtes limité dans votre capacité à utiliser l'héritage multiple:

        Base.__init__(self) # Avoid this.

J'explique plus loin ci-dessous.

"Quelle différence y a-t-il dans ce 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!

La principale différence dans ce code est que vous obtenez une couche d'indirection dans le __init__ avec super, qui utilise la classe en cours pour déterminer la prochaine classe __init__ de regarder dans le MRO.

J'illustre cette différence dans une réponse à la question canonique, Comment utiliser 'super' en Python?, qui démontre injection de dépendance et héritage multiple coopératif.

Si Python n'avait pas super

Voici un code qui est en fait très proche de super (comment il est implémenté en C, moins un certain comportement de vérification et de retour, et traduit en 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

Écrit un peu plus comme Python natif:

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

Si nous n'avions pas le super objet, nous devrions écrire ce code manuel partout (ou le recréer!) pour nous assurer que nous appelons la prochaine méthode appropriée dans l'ordre de résolution de la méthode!

Comment super fait-il cela dans Python 3 sans qu'on lui dise explicitement quelle classe et quelle instance de la méthode à partir de laquelle elle a été appelée?

Il obtient le cadre de la pile appelante et trouve la classe (implicitement stockée en tant que variable libre locale, __class__, faisant de la fonction appelante une fermeture sur la classe) et le premier argument de cette fonction, qui devrait être l'instance ou la classe qui l'informe de l'ordre de résolution de la méthode (MRO) à utiliser.

Comme il nécessite ce premier argument pour le MRO, en utilisant super avec des méthodes statiques est impossible.

Critiques d'autres réponses:

super () vous permet d'éviter de vous référer explicitement à la classe de base, ce qui peut être sympa. . Mais l'avantage principal vient avec l'héritage multiple, où toutes sortes de choses amusantes peuvent arriver. Voir les docs standard sur super si vous ne l'avez pas déjà fait.

C'est plutôt la main-wavey et ne nous dit pas beaucoup, mais le point de super n'est pas d'éviter d'écrire la classe parent. Le but est de s'assurer que la méthode suivante en ligne dans l'ordre de résolution de la méthode (MRO) est appelée. Cela devient important dans l'héritage multiple.

Je vais expliquer ici.

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

Et créons une dépendance que nous voulons être appelée après l'Enfant:

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

Maintenant, souviens-toi, ChildB utilise super, ChildA ne fait pas:

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

Et UserA n'appelle pas la méthode UserDependency:

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

Mais UserB, car ChildB les usages super, Est-ce que!:

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

Critique pour une autre réponse

En aucun cas, vous ne devriez faire ce qui suit, ce qu'une autre réponse suggère, car vous aurez certainement des erreurs lorsque vous sous-classez ChildB:

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

(Cette réponse n'est ni astucieuse, ni particulièrement intéressante, mais malgré les critiques directes dans les commentaires et plus de 17 downvotes, le répondeur a persisté à le suggérer jusqu'à ce qu'un gentil éditeur corrige son problème.)

Explication: Cette réponse suggère d'appeler super comme ceci:

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

C'est complètement faux. super permet de rechercher le parent suivant dans le MRO (voir la première section de cette réponse) pour les classes enfants. Si vous dites super nous sommes dans la méthode de l'instance enfant, elle recherchera ensuite la méthode suivante en ligne (probablement celle-ci) entraînant une récursion, provoquant probablement un échec logique (dans l'exemple du répondeur, c'est le cas) ou une RuntimeError lorsque la profondeur de récurrence est dépassée.

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



Il a été noté que dans Python 3.0+, vous pouvez utiliser

super().__init__() 

pour faire votre appel, ce qui est concis et ne vous oblige pas à référencer explicitement les noms de classe parent OU, ce qui peut être pratique. Je veux juste ajouter que pour Python 2.7 ou moins, il est possible d'obtenir ce comportement insensible au nom en écrivant self.__class__ au lieu du nom de la classe, c'est-à-dire

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

TOUTEFOIS, cela casse les appels à super pour les classes qui héritent de votre classe, où self.__class__ pourrait renvoyer une classe d'enfant. Par exemple:

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

Ici j'ai un cours Square, qui est une sous-classe de Rectangle. Dites que je ne veux pas écrire un constructeur séparé pour Square parce que le constructeur pour Rectangle est assez bon, mais pour une raison quelconque, je veux mettre en place un carré afin que je puisse réimplémenter une autre méthode.

Quand je crée un Square en utilisant mSquare = Square('a', 10,10), Python appelle le constructeur pour Rectangle parce que je n'ai pas donné Square son propre constructeur. Cependant, dans le constructeur pour Rectangle, l'appel super(self.__class__,self) va retourner la superclasse de mSquare, donc il appelle le constructeur pour Rectangle encore. C'est ainsi que se passe la boucle infinie, comme cela a été mentionné par @S_C. Dans ce cas, quand je cours super(...).__init__() J'appelle le constructeur pour Rectangle mais puisque je ne lui donne aucun argument, j'obtiendrai une erreur.


221
2017-10-08 20:08



Super n'a pas d'effets secondaires

Base = ChildB

Base()

fonctionne comme prévu

Base = ChildA

Base()

entre en récurrence infinie.


75
2017-11-27 23:26



Juste une tête ... avec Python 2.7, et je crois depuis super() a été introduit dans la version 2.2, vous pouvez seulement appeler super()si l'un des parents hérite d'une classe qui hérite finalement object (cours de style nouveau).

Personnellement, comme pour le code python 2.7, je vais continuer à utiliser BaseClassName.__init__(self, args) jusqu'à ce que j'ai l'avantage d'utiliser super().


68
2018-05-25 17:52



Il n'y a pas, vraiment. super() regarde la prochaine classe dans le MRO (ordre de résolution de la méthode, accessible avec cls.__mro__) pour appeler les méthodes. Juste appeler la base __init__ appelle la base __init__. Comme il arrive, le MRO a exactement un élément - la base. Donc, vous faites vraiment exactement la même chose, mais d'une manière plus agréable avec super() (surtout si vous entrez dans l'héritage multiple plus tard).


45
2018-02-23 00:34



La principale différence est que ChildA.__init__ appellera inconditionnellement Base.__init__ tandis que ChildB.__init__ appellera __init__ dans quelle que soit la classe se trouve être ChildB ancêtre dans selfLa lignée des ancêtres (qui peut différer de ce que vous attendez).

Si vous ajoutez un ClassC qui utilise l'héritage multiple:

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

puis Base n'est plus le parent de ChildB pour ChildC instances. À présent super(ChildB, self) pointera vers Mixin si self est un ChildC exemple.

Vous avez inséré Mixin entre ChildB et Base. Et vous pouvez en profiter avec super()

Donc, si vous avez conçu vos classes pour qu'elles puissent être utilisées dans un scénario d'héritage coopératif multiple, vous utilisez super parce que vous ne savez pas vraiment qui sera l'ancêtre à l'exécution.

le super super commentaire et Vidéo d'accompagnement de pycon 2015 explique cela plutôt bien.


22
2017-09-21 06:41