web-dev-qa-db-fra.com

Compréhension de Python super () avec les méthodes __init __ ()

J'essaie de comprendre l'utilisation de super(). De par son apparence, les deux classes enfants peuvent être créées, très bien.

Je suis curieux de connaître la différence entre les 2 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()
2312
Mizipzor

super() vous permet d'éviter de faire explicitement référence à la classe de base, qui peut être Nice. Mais le principal avantage est l’héritage multiple, où toutes sortes de choses amusantes peuvent se produire. Voir le documentation standard sur super si vous ne l'avez pas déjà fait.

Notez que la syntaxe a été modifiée dans Python 3. : vous pouvez simplement dire super().__init__() au lieu de super(ChildB, self).__init__() lequel IMO est un peu plus agréable. La documentation standard fait également référence à un guide d'utilisation de super () , ce qui est assez explicatif.

1676
Kiv

J'essaie de comprendre super()

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

Dans Python 3, on peut l'appeler ainsi:

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

Dans Python 2, nous devons l’utiliser comme ceci:

super(ChildB, self).__init__()

Sans super, votre capacité à utiliser l'héritage multiple est limitée:

Base.__init__(self) # Avoid this.

J'explique plus en 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'indirection dans le __init__ avec super, qui utilise la classe actuelle pour déterminer le __init__ de la classe suivante à rechercher dans le MRO.

J'illustre cette différence dans une réponse à la question canonique, Comment utiliser 'super' en Python? , qui illustre l'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 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 devrions écrire ce code manuel partout (ou le recréer!) Pour nous assurer que nous appelons la méthode suivante appropriée dans l'ordre de résolution de méthode!

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

Il récupère le cadre de pile appelant et trouve la classe (implicitement stockée sous forme de variable libre locale, __class__, transformant la fonction appelante en une fermeture sur la classe) et le premier argument de cette fonction, qui devrait être l'instance ou classe qui lui indique quel ordre de résolution de méthode (MRO) utiliser.

Puisqu'il nécessite ce premier argument pour le MRO, tiliser super avec des méthodes statiques est impossible .

Critiques d'autres réponses:

super () vous permet d'éviter de faire explicitement référence à la classe de base, qui peut être Nice. . Mais le principal avantage réside dans l'héritage multiple, où toutes sortes de choses amusantes peuvent se produire. Voir la documentation standard sur super si vous ne l'avez pas déjà fait.

C'est assez vague et ne nous dit pas grand chose, mais le but de super n'est pas d'éviter d'écrire la classe parente. 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 appeler après l'enfant:

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

Maintenant, souvenez-vous que ChildB utilise super, ChildA ne:

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, parce que ChildB utilise super, fait-il !:

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

Critique pour une autre réponse

Vous ne devez en aucun cas effectuer les opérations suivantes, ce qu'une autre réponse suggère, car vous obtiendrez certainement des erreurs lorsque vous sous-classerez ChildB:

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

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

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

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

C'est complètement faux. super nous permet de rechercher le prochain parent dans la MRO (voir la première section de cette réponse) pour les classes enfants. Si vous indiquez super que nous sommes dans la méthode de l'instance enfant, la méthode suivante en ligne (probablement celle-ci) sera recherchée, ce qui entraînera probablement une récursivité, ce qui provoquera probablement un échec logique (dans l'exemple du répondeur) ou a RuntimeError lorsque la profondeur de récursivité 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'
617
Aaron Hall

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

super().__init__()

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

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

CEPENDANT, cela interrompt les appels à super pour toutes les classes héritant de votre classe, où self.__class__ peut renvoyer une classe 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 de Rectangle est suffisant, mais pour une raison quelconque, je veux implémenter un Square afin de pouvoir réimplémenter une autre méthode.

Lorsque je crée un Square à l'aide de mSquare = Square('a', 10,10), Python appelle le constructeur pour Rectangle car je n'ai pas donné à Square son propre constructeur. Cependant, dans le constructeur de Rectangle, l'appel super(self.__class__,self) va renvoyer la superclasse de mSquare, de sorte qu'il appelle à nouveau le constructeur de Rectangle. Voici comment se produit la boucle infinie, comme mentionné par @S_C. Dans ce cas, lorsque j'exécute super(...).__init__(), j'appelle le constructeur pour Rectangle, mais comme je ne lui donne aucun argument, j'obtiens une erreur.

248
AnjoMan

Super n'a pas d'effets secondaires

Base = ChildB

Base()

fonctionne comme prévu

Base = ChildA

Base()

entre dans une récursion infinie.

80
S C

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

Personnellement, comme pour le code python 2.7, je vais continuer à utiliser BaseClassName.__init__(self, args) jusqu'à ce que je profite réellement de l'utilisation de super().

71
rgenito

Il n'y en a pas vraiment. super() regarde la classe suivante dans la MRO (ordre de résolution de la méthode, accessible avec cls.__mro__) pour appeler les méthodes. Un simple appel de la base __init__ appelle la base __init__. En l'occurrence, le MRO a exactement un élément: la base. Donc, vous faites vraiment exactement la même chose, mais de manière plus agréable avec super() (particulièrement si vous entrez dans l'héritage multiple plus tard).

51
Devin Jeanpierre

La principale différence est que ChildA.__init__ appellera inconditionnellement Base.__init__ alors que ChildB.__init__ appellera __init__ dans quelle que soit la classe qui serait ancêtre de ChildB dans la ligne de self ancêtres (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

then Base N'EST PLUS LE PARENT DE ChildB pour les instances ChildC. Maintenant, super(ChildB, self) désignera Mixin si self est une instance ChildC.

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

Ainsi, si vous avez conçu vos classes de sorte qu'elles puissent être utilisées dans un scénario Héritage multiple coopératif, vous utilisez super car vous ne savez pas vraiment qui sera l'ancêtre à l'exécution.

Les super post super considéré et vidéo d'accompagnement de pycon 2015 expliquent assez bien cela.

28
ecerulm