Pourquoi les méthodes superclass __init__ ne sont-elles pas automatiquement invoquées?

155

Pourquoi les concepteurs de Python ont-ils décidé que les __init__()méthodes des sous-classes n'appellent pas automatiquement les __init__()méthodes de leurs superclasses, comme dans d'autres langages? L'idiome pythonique et recommandé ressemble-t-il vraiment à ce qui suit?

class Superclass(object):
    def __init__(self):
        print 'Do something'

class Subclass(Superclass):
    def __init__(self):
        super(Subclass, self).__init__()
        print 'Do something else'
jrdioko
la source
2
Vous pouvez écrire un décorateur pour hériter de la __init__méthode, et peut-être même rechercher automatiquement des sous-classes et les décorer.
osa
2
@osa, semble une très bonne idée. Souhaitez-vous décrire un peu plus la partie décoratrice?
Diansheng
1
@osa oui décrivez plus!
Charlie Parker

Réponses:

163

La distinction essentielle entre Python __init__et les autres langues constructeurs est que __init__est pas un constructeur: c'est un initialiseur (le véritable constructeur ( le cas échéant, mais, plus tard ;-) est __new__et fonctionne complètement différemment à nouveau). Bien que la construction de toutes les superclasses (et, sans aucun doute, le faire "avant" de continuer à construire vers le bas) fait évidemment partie de la construction de l'instance d'une sous-classe, ce n'est clairement pas le cas pour l' initialisation, car il existe de nombreux cas d'utilisation dans lesquels l'initialisation des superclasses doit être ignorée, modifiée, contrôlée - se produisant, le cas échéant, "au milieu" de l'initialisation de la sous-classe, et ainsi de suite.

Fondamentalement, la délégation de super-classe de l'initialiseur n'est pas automatique en Python pour exactement les mêmes raisons qu'une telle délégation n'est également pas automatique pour les autres méthodes - et notez que ces "autres langages" ne font pas de délégation de super-classe automatique pour aucun autre méthode non plus ... juste pour le constructeur (et le cas échéant, le destructeur), qui, comme je l'ai mentionné, n'est pas ce que Python __init__est. (Le comportement de __new__est également assez particulier, bien que vraiment pas directement lié à votre question, car il __new__s'agit d'un constructeur si particulier qu'il n'a pas nécessairement besoin de construire quoi que ce soit - pourrait parfaitement renvoyer une instance existante, ou même une non-instance ... clairement Python vous offre beaucoupplus de contrôle de la mécanique que les "autres langages" que vous avez en tête, ce qui implique également de ne pas avoir de délégation automatique en __new__soi! -).

Alex Martelli
la source
7
Voté parce que pour moi, le véritable avantage est la possibilité que vous mentionnez d'appeler _ init () _ de la superclasse à tout moment de l'initialisation de la sous-classe (ou pas du tout).
kindall
53
-1 pour " __init__n'est pas un constructeur ... le constructeur réel ... est __new__". Comme vous le remarquez vous-même, __new__ne se comporte en rien comme un constructeur d'autres langages. __init__est en fait très similaire (il est appelé lors de la création d'un nouvel objet, après que cet objet est alloué, pour définir des variables membres sur le nouvel objet), et est presque toujours l'endroit pour implémenter des fonctionnalités que dans d'autres langages vous mettriez un constructeur. Alors appelez cela un constructeur!
Ben
8
Je pense aussi que c'est une affirmation plutôt absurde: " construire toutes les superclasses ... fait évidemment partie de dire que vous construisez une instance de sous-classe, ce n'est clairement pas le cas pour l' initialisation ". Il n'y a rien dans les mots construction / initialisation qui rend cela "évident" pour quiconque. Et __new__n'invoque pas automatiquement la superclasse __new__non plus. Donc, votre affirmation selon laquelle la distinction clé est que la construction implique nécessairement la construction de superclasses, alors que l' initialisation n'est pas incompatible avec votre affirmation qui __new__est le constructeur.
Ben
36
En fait, à partir de la documentation python pour __init__à docs.python.org/reference/datamodel.html#basic-customization : "En tant que contrainte spéciale sur les constructeurs , aucune valeur ne peut être retournée; cela entraînera la levée d'une TypeError lors de l'exécution "(c'est moi qui souligne). Alors là, c'est officiel, __init__c'est un constructeur.
Ben
3
Dans la terminologie Python / Java, __init__s'appelle un constructeur. Ce constructeur est une fonction d'initialisation appelée après que l'objet a été complètement construit et initialisé à un état par défaut, y compris son type d'exécution final. Il n'est pas équivalent aux constructeurs C ++, qui sont appelés sur un objet alloué de type statique avec un état non défini. Celles-ci sont également différentes de __new__, donc nous avons vraiment au moins quatre types différents de fonctions d'allocation / construction / initialisation. Les langages utilisent une terminologie mixte, et la partie importante est le comportement et non la terminologie.
Elazar
36

Je suis un peu gêné quand les gens perroquent le "Zen of Python", comme si c'était une justification pour quoi que ce soit. C'est une philosophie de conception; les décisions de conception particulières peuvent toujours être expliquées en termes plus spécifiques - et elles doivent l'être, sinon le "Zen of Python" devient une excuse pour faire quoi que ce soit.

La raison est simple: vous ne construisez pas nécessairement une classe dérivée d'une manière similaire à la façon dont vous construisez la classe de base. Vous pouvez avoir plus de paramètres, moins, ils peuvent être dans un ordre différent ou pas du tout liés.

class myFile(object):
    def __init__(self, filename, mode):
        self.f = open(filename, mode)
class readFile(myFile):
    def __init__(self, filename):
        super(readFile, self).__init__(filename, "r")
class tempFile(myFile):
    def __init__(self, mode):
        super(tempFile, self).__init__("/tmp/file", mode)
class wordsFile(myFile):
    def __init__(self, language):
        super(wordsFile, self).__init__("/usr/share/dict/%s" % language, "r")

Cela s'applique à toutes les méthodes dérivées, pas seulement __init__.

Glenn Maynard
la source
6
Cet exemple offre-t-il quelque chose de spécial? les langues statiques peuvent le faire aussi
jean
18

Java et C ++ exigent qu'un constructeur de classe de base soit appelé en raison de la disposition de la mémoire.

Si vous avez une classe BaseClassavec un membre field1et que vous créez une nouvelle classe SubClassqui ajoute un membre field2, une instance de SubClasscontient un espace pour field1et field2. Vous avez besoin d'un constructeur de BaseClasspour remplir field1, sauf si vous avez besoin que toutes les classes héritées répètent BaseClassl'initialisation de leur propre constructeur. Et si field1c'est privé, alors les classes héritées ne peuvent pas s'initialiser field1.

Python n'est pas Java ou C ++. Toutes les instances de toutes les classes définies par l'utilisateur ont la même «forme». Ce sont essentiellement des dictionnaires dans lesquels des attributs peuvent être insérés. Avant qu'une initialisation n'ait été effectuée, toutes les instances de toutes les classes définies par l'utilisateur sont presque exactement les mêmes ; ce ne sont que des endroits pour stocker des attributs qui n'en stockent pas encore.

Il est donc parfaitement logique pour une sous-classe Python de ne pas appeler son constructeur de classe de base. Il pourrait simplement ajouter les attributs lui-même s'il le voulait. Il n'y a pas d'espace réservé pour un nombre donné de champs pour chaque classe dans la hiérarchie, et il n'y a aucune différence entre un attribut ajouté par code à partir d'une BaseClassméthode et un attribut ajouté par code à partir d'une SubClassméthode.

Si, comme cela est courant, vous SubClassvoulez vraiment que tous BaseClassles invariants de s soient configurés avant de procéder à sa propre personnalisation, alors oui, vous pouvez simplement appeler BaseClass.__init__()(ou utiliser super, mais c'est compliqué et a parfois ses propres problèmes). Mais vous n'êtes pas obligé. Et vous pouvez le faire avant, ou après, ou avec différents arguments. Enfer, si vous le vouliez, vous pourriez appeler le BaseClass.__init__d'une autre méthode que __init__; peut-être que vous avez une initialisation paresseuse bizarre en cours.

Python atteint cette flexibilité en gardant les choses simples. Vous initialisez les objets en écrivant une __init__méthode qui définit les attributs sur self. C'est tout. Elle se comporte exactement comme une méthode, car c'est exactement une méthode. Il n'y a pas d'autres règles étranges et peu intuitives sur les choses à faire en premier, ou les choses qui se produiront automatiquement si vous ne faites pas d'autres choses. Le seul but qu'il doit servir est d'être un hook à exécuter pendant l'initialisation de l'objet pour définir les valeurs d'attribut initiales, et c'est exactement ce qu'il fait. Si vous voulez qu'il fasse autre chose, vous l'écrivez explicitement dans votre code.

Ben
la source
2
Quant au C ++, il n'a rien à voir avec la "disposition de la mémoire". Le même modèle d'initialisation que les offres Python aurait pu être implémenté en C ++. La seule raison pour laquelle la construction / destruction est telle qu'elle est en C ++ est la décision de conception de fournir des installations fiables et fonctionnelles pour la gestion des ressources (RAII), qui pourraient également être générées automatiquement (ce qui signifie moins de code et d'erreurs humaines) par les compilateurs puisque leurs règles (leur ordre d'appel) sont rigoureusement définies. Pas sûr, mais Java a probablement simplement suivi cette approche pour être un autre langage semblable à POLA C.
Alexander Shukaev
10

"Explicite vaut mieux qu'implicite." C'est le même raisonnement qui indique que nous devrions explicitement écrire «soi».

Je pense qu'en fin de compte, c'est un avantage - pouvez-vous réciter toutes les règles de Java concernant l'appel des constructeurs de superclasses?

Mike Axiak
la source
8
Je suis d'accord avec vous pour la plupart, mais la règle de Java est en fait assez simple: le constructeur no-arg est invoqué à moins que vous n'en demandiez spécifiquement un autre.
Laurence Gonsalves
1
@Laurence - Que se passe-t-il lorsque la classe parente ne définit pas de constructeur sans argument? Que se passe-t-il lorsque le constructeur no-arg est protégé ou privé?
Mike Axiak
8
Exactement la même chose qui se produirait si vous essayiez de l'appeler explicitement.
Laurence Gonsalves
8

Souvent, la sous-classe a des paramètres supplémentaires qui ne peuvent pas être passés à la superclasse.

John La Rooy
la source
7

Pour le moment, nous avons une page assez longue décrivant l'ordre de résolution des méthodes en cas d'héritage multiple: http://www.python.org/download/releases/2.3/mro/

Si les constructeurs étaient appelés automatiquement, vous auriez besoin d'une autre page d'au moins la même longueur expliquant l'ordre dans lequel cela se produit. Ce serait l'enfer ...

viraptor
la source
C'était la bonne réponse. Python aurait besoin de définir la sémantique des arguments passant entre la sous-classe et la superclasse. Dommage que cette réponse ne soit pas votée. Peut-être que si cela montrait le problème avec quelques exemples?
Gary Weiss
5

Pour éviter toute confusion, il est utile de savoir que vous pouvez invoquer la __init__()méthode base_class si child_class n'a pas de __init__()classe.

Exemple:

class parent:
  def __init__(self, a=1, b=0):
    self.a = a
    self.b = b

class child(parent):
  def me(self):
    pass

p = child(5, 4)
q = child(7)
z= child()

print p.a # prints 5
print q.b # prints 0
print z.a # prints 1

En fait, le MRO en python cherchera __init__()dans la classe parent quand il ne peut pas le trouver dans la classe children. Vous devez appeler le constructeur de classe parent directement si vous avez déjà une __init__()méthode dans la classe enfants.

Par exemple, le code suivant renverra une erreur: class parent: def init (self, a = 1, b = 0): self.a = a self.b = b

    class child(parent):
      def __init__(self):
        pass
      def me(self):
        pass

    p = child(5, 4) # Error: constructor gets one argument 3 is provided.
    q = child(7)  # Error: constructor gets one argument 2 is provided.

    z= child()
    print z.a # Error: No attribute named as a can be found.
Keyvanrm
la source
3

C'est peut __init__- être la méthode que la sous-classe doit remplacer. Parfois, les sous-classes ont besoin que la fonction du parent s'exécute avant d'ajouter du code spécifique à la classe, et d'autres fois, elles doivent configurer des variables d'instance avant d'appeler la fonction du parent. Puisqu'il n'y a aucun moyen pour Python de savoir quand il serait le plus approprié d'appeler ces fonctions, il ne devrait pas deviner.

Si cela ne vous influence pas, considérez que __init__c'est juste une autre fonction. Si la fonction en question l'était à la dostuffplace, voudriez-vous toujours que Python appelle automatiquement la fonction correspondante dans la classe parent?

Kirk Strauser
la source
2

Je crois que la seule considération très importante ici est qu'avec un appel automatique à super.__init__(), vous proscrivez, par conception, quand cette méthode d'initialisation est appelée, et avec quels arguments. éviter de l'appeler automatiquement, et exiger du programmeur qu'il fasse cet appel explicitement, implique beaucoup de flexibilité.

après tout, ce n'est pas parce que la classe B est dérivée de la classe A que l' A.__init__()on peut ou doit être appelée avec les mêmes arguments que B.__init__(). rendre l'appel explicite signifie qu'un programmeur peut, par exemple, définir B.__init__()avec des paramètres complètement différents, faire des calculs avec ces données, appeler A.__init__()avec des arguments appropriés pour cette méthode, puis faire un post-traitement. ce type de flexibilité serait difficile à atteindre s'il A.__init__()était appelé B.__init__()implicitement, soit avant l' B.__init__()exécution, soit juste après.

couler
la source