__Init __ () doit-il appeler __init __ () de la classe parente?


132

Je suis utilisé en Objective-C, j'ai cette construction:

- (void)init {
    if (self = [super init]) {
        // init class
    }
    return self;
}

Python devrait-il également appeler l'implémentation de la classe parente pour __init__?

class NewClass(SomeOtherClass):
    def __init__(self):
        SomeOtherClass.__init__(self)
        # init class

Est-ce également vrai / faux pour __new__()et __del__()?

Edit: il y a une question très similaire: héritage et remplacement __init__en Python


vous avez changé votre code de manière significative. Je peux comprendre que l'original objectétait une faute de frappe. Mais maintenant, vous n'avez même pas le supertitre de votre question.
SilentGhost

Je pensais juste que super est utilisé comme nom pour la classe parent. Je ne pensais pas que quiconque penserait à la fonction. Je suis désolé pour tout malentendu.
Georg Schölly

Réponses:


67

En Python, appeler la super-classe ' __init__est facultatif. Si vous l'appelez, il est alors également facultatif d'utiliser l' superidentifiant ou de nommer explicitement la super classe:

object.__init__(self)

En cas d'objet, l'appel de la super méthode n'est pas strictement nécessaire, car la super méthode est vide. Pareil pour __del__.

D'autre part, pour __new__, vous devez en effet appeler la super méthode et utiliser son retour comme objet nouvellement créé - à moins que vous ne souhaitiez explicitement renvoyer quelque chose de différent.


Il n'y a donc pas de convention pour appeler simplement l'implémentation de super?
Georg Schölly

5
Dans les classes à l'ancienne, vous ne pouviez appeler le super init que si la super classe avait un init défini (ce qui n'est souvent pas le cas). Par conséquent, les gens pensent généralement à appeler la super méthode, plutôt que de le faire par principe.
Martin v.Löwis le

1
Si la syntaxe en python était aussi simple que [super init], ce serait plus courant. Juste une pensée spéculative; la super construction en Python 2.x est un peu gênante pour moi.
u0b34a0f6ae

Voici un exemple intéressant (et peut-être contradictoire): bytes.com/topic/python/answers/… init
mlvljr

"facultatif" en ce sens que vous n'avez pas besoin de l'appeler, mais si vous ne l'appelez pas, il ne sera pas appelé automatiquement.
McKay

140

Si vous avez besoin de faire quelque chose de super __init__en plus de ce qui est fait dans la classe actuelle, __init__,vous devez l'appeler vous-même, car cela ne se produira pas automatiquement. Mais si vous n'avez besoin de rien de super, __init__,pas besoin de l'appeler. Exemple:

>>> class C(object):
        def __init__(self):
            self.b = 1


>>> class D(C):
        def __init__(self):
            super().__init__() # in Python 2 use super(D, self).__init__()
            self.a = 1


>>> class E(C):
        def __init__(self):
            self.a = 1


>>> d = D()
>>> d.a
1
>>> d.b  # This works because of the call to super's init
1
>>> e = E()
>>> e.a
1
>>> e.b  # This is going to fail since nothing in E initializes b...
Traceback (most recent call last):
  File "<pyshell#70>", line 1, in <module>
    e.b  # This is going to fail since nothing in E initializes b...
AttributeError: 'E' object has no attribute 'b'

__del__est la même manière, (mais méfiez-vous de vous fier à la __del__finalisation - envisagez de le faire via l'instruction with à la place).

J'utilise rarement __new__. je fais toute l'initialisation en__init__.


3
La définition de la classe D (C) doit être corrigée comme çasuper(D,self).__init__()
eyquem

11
super () .__ init __ () ne fonctionne qu'en Python 3. Dans Python 2, vous avez besoin de super (D, self) .__ init __ ()
Jacinda

"Si vous avez besoin de quelque chose de l' init de super ..." - Ceci est une déclaration très problématique car il ne s'agit pas de savoir si le you / the subclass a besoin de "quelque chose", mais si la classe de base a besoin de quelque chose pour être valide instance de classe de base et fonctionnent correctement. En tant qu'implémenteur de la classe dérivée, les éléments internes de la classe de base sont des choses que vous ne pouvez / ne devriez pas savoir, et même si vous le faites parce que vous avez écrit les deux ou que les éléments internes sont documentés, la conception de la base peut changer à l'avenir et se rompre en raison d'une mauvaise écriture. Classe dérivée. Assurez-vous donc toujours que la classe de base est entièrement initialisée.
Nick le

105

Dans la réponse d'Anon:
"Si vous avez besoin de faire quelque chose de super __init__en plus de ce qui est fait dans la classe actuelle __init__, vous devez l'appeler vous-même, car cela ne se produira pas automatiquement"

C'est incroyable: il formule exactement le contraire du principe de l'héritage.


Ce n'est pas que "quelque chose de super __init__ (...) ne se produira pas automatiquement" , c'est que cela se produira automatiquement, mais cela n'arrivera pas parce que la classe de base ' __init__est remplacée par la définition de la classe dérivée__init__

Alors, POURQUOI définir une classe_ dérivée ' __init__, puisqu'elle remplace ce qui est visé quand quelqu'un recourt à l'héritage ??

C'est parce qu'il faut définir quelque chose qui n'est PAS fait dans la classe de base ' __init__, et la seule possibilité d'obtenir cela est de mettre son exécution dans une __init__fonction de classe dérivée' .
En d'autres termes, il faut quelque chose dans la classe de base ' __init__en plus de ce qui serait automatiquement fait dans la classe de base' __init__si cette dernière n'était pas remplacée.
PAS le contraire.


Ensuite, le problème est que les instructions souhaitées présentes dans la classe de base ' __init__ne sont plus activées au moment de l'instanciation. Afin de compenser cette inactivation, quelque chose de spécial est nécessaire: appeler explicitement la classe de base ' __init__, afin de GARDER , NE PAS AJOUTER, l'initialisation effectuée par la classe de base' __init__. C'est exactement ce qui est dit dans la doc officielle:

Une méthode de substitution dans une classe dérivée peut en fait vouloir étendre plutôt que simplement remplacer la méthode de classe de base du même nom. Il existe un moyen simple d'appeler directement la méthode de classe de base: il suffit d'appeler BaseClassName.methodname (self, arguments).
http://docs.python.org/tutorial/classes.html#inheritance

C'est toute l'histoire:

  • lorsque le but est de GARDER l'initialisation effectuée par la classe de base, c'est-à-dire l'héritage pur, rien de spécial n'est nécessaire, il faut juste éviter de définir une __init__fonction dans la classe dérivée

  • lorsque le but est de REMPLACER l'initialisation effectuée par la classe de base, __init__doit être définie dans la classe dérivée

  • lorsque le but est d'ajouter des processus à l'initialisation effectuée par la classe de base, une classe dérivée ' __init__ doit être définie, comprenant un appel explicite à la classe de base__init__


Ce que je ressens étonnant dans le poste d'Anon, ce n'est pas seulement qu'il exprime le contraire de la théorie de l'héritage, mais qu'il y a eu 5 mecs qui passent par ce vote à la hausse sans tourner les cheveux, et de plus il n'y a eu personne pour réagir en 2 ans en un fil dont le sujet intéressant doit être lu relativement souvent.


1
J'étais sûr que ce message allait être voté pour. J'ai peur de ne pas avoir beaucoup d'explications sur les raisons. Il est plus facile de décliner que d'analyser un texte qui semble incompréhensible. J'ai eu un long moment à essayer de comprendre le message d'Anon avant de finalement réaliser qu'il était spécifiquement écrit et pas vraiment faisant autorité. Peut-être que cela peut être interprété comme approximativement juste pour quelqu'un qui connaît l'héritage; mais je trouve cela déroutant lorsqu'il est lu par quelqu'un ayant des notions instables de l'héritage, un sujet pas aussi clair que l'eau de roche en général
eyquem

2
"J'étais sûr que ce message allait être voté pour ..." Le problème principal est que vous êtes un peu en retard à la fête, et la plupart des gens ne liront peut-être pas au-delà des premières réponses. Grande explication au fait +1
Gerrat

Bonne réponse c'est.
Trilarion

3
Vous avez manqué les mots importants «en plus» dans la phrase que vous avez citée d'Aaron. La déclaration d'Aaron est tout à fait correcte et correspond à ce que vous finissez par dire.
GreenAsJade

1
C'est la première explication qui a rendu le choix de conception de python logique.
Joseph Garvin

20

Edit : (après le changement de code)
Il n'y a aucun moyen pour nous de vous dire si vous devez ou non appeler vos parents __init__(ou toute autre fonction). L'héritage fonctionnerait évidemment sans un tel appel. Tout dépend de la logique de votre code: par exemple, si tout ce que vous __init__faites est fait dans la classe parente, vous pouvez simplement ignorer __init__complètement la classe enfant .

Prenons l'exemple suivant:

>>> class A:
    def __init__(self, val):
        self.a = val


>>> class B(A):
    pass

>>> class C(A):
    def __init__(self, val):
        A.__init__(self, val)
        self.a += val


>>> A(4).a
4
>>> B(5).a
5
>>> C(6).a
12

J'ai supprimé le super appel de mon exemple, je voulais savoir si l'on devait appeler l'implémentation d' init de la classe parente ou non.
Georg Schölly

vous voudrez peut-être modifier le titre alors. mais ma réponse tient toujours.
SilentGhost

5

Il n'y a pas de règle absolue. La documentation d'une classe doit indiquer si les sous-classes doivent appeler la méthode de la superclasse. Parfois, vous voulez remplacer complètement le comportement de la superclasse, et à d'autres moments l'augmenter - c'est-à-dire appeler votre propre code avant et / ou après un appel de superclasse.

Mise à jour: la même logique de base s'applique à tout appel de méthode. Les constructeurs ont parfois besoin d'une attention particulière (car ils mettent souvent en place un état qui détermine le comportement) et les destructeurs parce qu'ils sont parallèles aux constructeurs (par exemple dans l'allocation des ressources, par exemple les connexions de base de données). Mais la même chose pourrait s'appliquer, par exemple, à la render()méthode d'un widget.

Mise à jour supplémentaire: qu'est-ce que l'OPP? Voulez-vous dire POO? Non - une sous-classe a souvent besoin de savoir quelque chose sur la conception de la superclasse. Pas les détails de mise en œuvre interne - mais le contrat de base que la superclasse a avec ses clients (en utilisant des classes). Cela ne viole en aucun cas les principes de la POO. C'est pourquoi protectedest un concept valide en POO en général (mais pas, bien sûr, en Python).


Vous avez dit que parfois on voudrait appeler son propre code avant l'appel de la superclasse. Pour ce faire, il faut connaître l'implémentation de la classe parente, ce qui violerait l'OPP.
Georg Schölly

4

IMO, vous devriez l'appeler. Si votre superclasse l'est object, vous ne devriez pas, mais dans d'autres cas, je pense qu'il est exceptionnel de ne pas l'appeler. Comme d'autres l'ont déjà répondu, c'est très pratique si votre classe n'a même pas à se redéfinir __init__, par exemple lorsqu'elle n'a pas d'état interne (supplémentaire) à initialiser.


2

Oui, vous devez toujours appeler __init__explicitement la classe de base comme une bonne pratique de codage. Oublier de le faire peut entraîner des problèmes subtils ou des erreurs d'exécution. Ceci est vrai même si __init__ne prend aucun paramètre. Ceci est différent des autres langages où le compilateur appellerait implicitement le constructeur de classe de base pour vous. Python ne fait pas ça!

La principale raison de toujours appeler la classe de base _init__est que la classe de base peut généralement créer des variables membres et les initialiser aux valeurs par défaut. Donc, si vous n'appelez pas la classe de base init, aucun de ce code ne sera exécuté et vous vous retrouverez avec une classe de base qui n'a pas de variables membres.

Exemple :

class Base:
  def __init__(self):
    print('base init')

class Derived1(Base):
  def __init__(self):
    print('derived1 init')

class Derived2(Base):
  def __init__(self):
    super(Derived2, self).__init__()
    print('derived2 init')

print('Creating Derived1...')
d1 = Derived1()
print('Creating Derived2...')
d2 = Derived2()

Cela imprime.

Creating Derived1...
derived1 init
Creating Derived2...
base init
derived2 init

Exécutez ce code .

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.