Comprendre les méthodes Python super() avec init () [dupliquer]

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

j'essaie de comprendre l'utilisation de super() . Apparemment, les deux classes d'enfants peuvent être créées, très bien.

je suis curieux de savoir à propos de la réelle différence entre les 2 classes enfant.

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()
2041
demandé sur U9-Forward 2009-02-23 03:30:08
la source

7 ответов

super() permet d'éviter de faire explicitement référence à la classe de base, ce qui peut être agréable. Mais le principal avantage vient avec l'héritage multiple, où toutes sortes de fun stuff peut se produire. Voir le docs standard sur super si vous ne l'avez pas déjà.

notez que la syntaxe a changé en Python 3.0 : vous pouvez simplement dire super().__init__() au lieu de super(ChildB, self).__init__() qui IMO est un peu plus sympa. Le la norme docs se réfère également à un guide d'utilisation de super () qui est très explicatif.

1463
répondu Kiv 2018-10-11 18:46:47
la source

j'essaie de comprendre super()

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

en Python 3, Nous pouvons 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.

Je m'explique plus en détail ci-dessous.

" quelle différence y a-t-il réellement 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'indirecte dans le __init__ avec super , qui utilise la classe courante pour déterminer la prochaine classe __init__ pour 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 le code qui est en fait très proche de super (comment il est implémenté en C, moins certains comportements de vérification et de repli, 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 l'objet super , nous aurions à écrire ce code manuel partout (ou le recréer!) pour s'assurer que nous appelons la méthode suivante appropriée dans L'ordre de résolution de méthode!

comment super fait - il cela en Python 3 sans être dit explicitement quelle classe et instance de la méthode il a été appelé à partir?

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

Puisqu'il exige que le 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 faire référence à la classe de base explicitement, ce qui peut être agréable. . Mais le principal avantage vient de l'héritage multiple, où toutes sortes de choses peuvent arriver. Voir les docs standard sur super si vous ne l'avez pas déjà.

c'est plutôt ondulé à la main et ça ne nous dit pas grand chose, mais le but de super n'est pas d'éviter d'écrire la classe des parents. Il s'agit de s'assurer que la prochaine méthode dans l'ordre de la méthode de résolution (MRO) est appelée. Cela devient important dans l'héritage multiple.

je vais vous expliquer.

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 appeler comme L'enfant:

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

maintenant rappelez-vous, ChildB utilise super, ChildA n'est 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 de dépendance Userd:

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

mais UserB , parce que ChildB utilise super , fait!:

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

les Critiques pour une autre réponse

en aucun cas vous ne devez faire ce qui suit, ce qu'une autre réponse suggère, car vous obtiendrez certainement des erreurs lorsque vous sous-classe ChildB:

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

(cette réponse n'est pas intelligente ni particulièrement intéressante, mais en dépit de la critique directe dans les commentaires et plus de 17 commentaires négatifs, le répondeur a persisté à La suggérer jusqu'à ce qu'un gentil rédacteur ait réglé 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 regarder le prochain parent dans le MRO (voir la première section de cette réponse) pour les classes pour enfants. Si vous dites super nous sommes dans la méthode de l'instance de l'enfant, il va alors chercher la méthode suivante en ligne (probablement celle-ci) résultant en une récursion, causant probablement un échec logique (dans l'exemple du répondeur, il fait) ou un RuntimeError lorsque la profondeur de la récursion 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'
452
répondu Aaron Hall 2017-12-03 02:10:30
la source

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

super().__init__()

pour faire votre appel, qui est concis et ne vous oblige pas à faire référence aux noms de parent ou de classe explicitement, 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 classe, i.e.

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

cependant, cela brise les appels à super pour toutes les classes qui héritent de votre classe, où self.__class__ pourrait retourner 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 une classe Square , qui est une sous-classe de Rectangle . Disons que je ne veux pas écrire un constructeur séparé pour Square parce que le constructeur pour Rectangle est suffisant, mais pour quelque raison que ce soit je veux implémenter un carré pour que je puisse réimposer une autre méthode.

Quand Je créer 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 à nouveau. C'est ainsi que la boucle infinie se produit, comme l'a mentionné @s_c. Dans ce cas, quand j'exécute super(...).__init__() j'appelle le constructeur pour Rectangle mais puisque je donne il n'y a pas d'arguments, je vais avoir une erreur.

224
répondu AnjoMan 2017-12-05 06:31:03
la source

Super n'a pas d'effets secondaires

Base = ChildB

Base()

fonctionne comme prévu

Base = ChildA

Base()

entre en récursion infinie.

75
répondu S C 2012-11-28 03:55:53
la source

juste un avertissement... avec Python 2.7, et je crois que depuis que super() a été introduit dans la version 2.2, on ne peut appeler super() que si l'un des parents hérite d'une classe qui hérite finalement object ( new-style classes ).

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

68
répondu rgenito 2012-08-07 22:05:43
la source

il n'y en a pas, vraiment. super() examine la classe suivante dans le MRO (method resolution order, accessed with cls.__mro__ ) pour appeler les méthodes. Il suffit d'appeler la base __init__ pour appeler la base __init__ . Il se trouve que le MRO A exactement un élément-- la base. Donc, vous faites vraiment la même chose, mais d'une manière plus agréable avec super() (en particulier si vous obtenez dans l'héritage multiple plus tard).

47
répondu Devin Jeanpierre 2009-02-23 03:34:57
la source

la principale différence est que ChildA.__init__ appellera inconditionnellement Base.__init__ tandis que ChildB.__init__ appellera __init__ dans quelle que soit la classe qui se trouve être ChildB ancêtre dans self ligne des ancêtres de (qui peuvent 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. Maintenant super(ChildB, self) pointera vers Mixin si self est une instance ChildC .

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

donc si vous êtes conçu vos classes de sorte qu'elles puissent être utilisées dans un scénario coopératif D'héritage Multiple, vous utilisez super parce que vous ne faites pas vraiment de savoir qui va être l'ancêtre lors de l'exécution.

le super considered super post et pycon 2015 accompagnant vidéo expliquer assez bien.

23
répondu ecerulm 2017-10-26 21:35:22
la source

Autres questions sur python class oop inheritance super