Héritage et remplacement de __init__ en python

129

Je lisais 'Dive Into Python' et dans le chapitre sur les classes, il donne cet exemple:

class FileInfo(UserDict):
    "store file metadata"
    def __init__(self, filename=None):
        UserDict.__init__(self)
        self["name"] = filename

L'auteur dit ensuite que si vous souhaitez remplacer la __init__méthode, vous devez appeler explicitement le parent __init__avec les paramètres corrects.

  1. Et si cette FileInfoclasse avait plus d'une classe d'ancêtres?
    • Dois-je appeler explicitement toutes les __init__méthodes des classes ancêtres ?
  2. Dois-je également le faire pour toute autre méthode que je souhaite remplacer?
liewl
la source
3
Notez que la surcharge est un concept distinct du remplacement.
Dana the Sane

Réponses:

158

Le livre est un peu daté en ce qui concerne les appels de sous-classe-superclasse. Il est également un peu daté en ce qui concerne la sous-classification des classes intégrées.

Cela ressemble à ceci de nos jours:

class FileInfo(dict):
    """store file metadata"""
    def __init__(self, filename=None):
        super(FileInfo, self).__init__()
        self["name"] = filename

Notez ce qui suit:

  1. Nous pouvons sous - classe directement intégrée dans les classes, comme dict, list, tuple, etc.

  2. La superfonction gère le suivi des superclasses de cette classe et l'appel des fonctions dans celles-ci de manière appropriée.

S.Lott
la source
5
devrais-je chercher un meilleur livre / tutoriel?
liewl
2
Donc, dans le cas d'héritage multiple, est-ce que super () les traque tous en votre nom?
Dana
dict .__ init __ (self), en fait, mais il n'y a rien de mal à cela - l'appel super (...) fournit simplement une syntaxe plus cohérente. (Je ne sais pas comment cela fonctionne pour l'héritage multiple, je pense qu'il ne peut trouver qu'une seule init superclasse )
David Z
4
L'intention de super () est de gérer l'héritage multiple. L'inconvénient est qu'en pratique l'héritage multiple se rompt toujours très facilement (voir < fuhm.net/super-harmful ).
qc
2
Oui, en cas d'héritage multiple et de classes de base prenant des arguments de constructeur, vous vous retrouvez généralement à appeler les constructeurs manuellement.
Torsten Marek
18

Dans chaque classe dont vous devez hériter, vous pouvez exécuter une boucle de chaque classe qui doit être initiée lors de l'initiation de la classe enfant ... un exemple qui peut être copié pourrait être mieux compris ...

class Female_Grandparent:
    def __init__(self):
        self.grandma_name = 'Grandma'

class Male_Grandparent:
    def __init__(self):
        self.grandpa_name = 'Grandpa'

class Parent(Female_Grandparent, Male_Grandparent):
    def __init__(self):
        Female_Grandparent.__init__(self)
        Male_Grandparent.__init__(self)

        self.parent_name = 'Parent Class'

class Child(Parent):
    def __init__(self):
        Parent.__init__(self)
#---------------------------------------------------------------------------------------#
        for cls in Parent.__bases__: # This block grabs the classes of the child
             cls.__init__(self)      # class (which is named 'Parent' in this case), 
                                     # and iterates through them, initiating each one.
                                     # The result is that each parent, of each child,
                                     # is automatically handled upon initiation of the 
                                     # dependent class. WOOT WOOT! :D
#---------------------------------------------------------------------------------------#



g = Female_Grandparent()
print g.grandma_name

p = Parent()
print p.grandma_name

child = Child()

print child.grandma_name
Bug de code
la source
2
Il ne semble pas que la boucle for in Child.__init__soit nécessaire. Quand je le retire de l'exemple, mon enfant imprime toujours "Grandma". L'init grand-parent n'est-elle pas gérée par la Parentclasse?
Adam
4
Je pense que les init de grands-parents sont déjà gérés par l'init de Parent, n'est-ce pas?
johk95
15

Vous n'avez pas vraiment besoin d'appeler les __init__méthodes de la (des) classe (s) de base, mais vous voulez généralement le faire parce que les classes de base y effectueront des initialisations importantes qui sont nécessaires pour que les autres méthodes de classes fonctionnent.

Pour les autres méthodes, cela dépend de vos intentions. Si vous voulez simplement ajouter quelque chose au comportement des classes de base, vous voudrez appeler la méthode des classes de base en plus de votre propre code. Si vous souhaitez modifier fondamentalement le comportement, vous ne pouvez pas appeler la méthode de la classe de base et implémenter toutes les fonctionnalités directement dans la classe dérivée.

qc
la source
4
Par souci d'exhaustivité technique, certaines classes, comme threading.Thread, généreront des erreurs gigantesques si jamais vous essayez d'éviter d'appeler init du parent .
David Berger
5
Je trouve tout ce discours «vous n'avez pas besoin d'appeler le constructeur de la base» extrêmement irritant. Vous n'êtes pas obligé de l'appeler dans une langue que je connais. Tous vont bousiller (ou pas) de la même manière, en n'initialisant pas les membres. Suggérer de ne pas initialiser les classes de base est tout simplement faux à bien des égards. Si une classe n'a pas besoin d'être initialisée maintenant, elle en aura besoin à l'avenir. Le constructeur fait partie de l'interface de la structure de classe / construction de langage et doit être utilisé correctement. Son utilisation correcte est de l'appeler parfois dans le constructeur de votre dérivé. Alors faites-le.
AndreasT
2
"Suggérer de ne pas initialiser les classes de base est tout simplement faux à bien des égards." Personne n'a suggéré de ne pas initialiser la classe de base. Lisez attentivement la réponse. Tout est question d'intention. 1) Si vous souhaitez laisser la logique init de la classe de base telle quelle, vous ne remplacez pas la méthode init dans votre classe dérivée. 2) Si vous souhaitez étendre la logique init à partir de la classe de base, vous définissez votre propre méthode init, puis appelez la méthode init de la classe de base à partir de celle-ci. 3) Si vous voulez remplacer la logique init de la classe de base, vous définissez votre propre méthode init sans appeler celle de la classe de base.
wombatonfire
4

Si la classe FileInfo a plus d'une classe ancêtre, vous devez absolument appeler toutes leurs fonctions __init __ (). Vous devez également faire de même pour la fonction __del __ (), qui est un destructeur.

moinudin
la source
2

Oui, vous devez appeler __init__pour chaque classe parent. Il en va de même pour les fonctions, si vous remplacez une fonction qui existe dans les deux parents.

vezult
la source