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'
__init__
méthode, et peut-être même rechercher automatiquement des sous-classes et les décorer.Réponses:
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! -).la source
__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!__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.__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.__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.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.
Cela s'applique à toutes les méthodes dérivées, pas seulement
__init__
.la source
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
BaseClass
avec un membrefield1
et que vous créez une nouvelle classeSubClass
qui ajoute un membrefield2
, une instance deSubClass
contient un espace pourfield1
etfield2
. Vous avez besoin d'un constructeur deBaseClass
pour remplirfield1
, sauf si vous avez besoin que toutes les classes héritées répètentBaseClass
l'initialisation de leur propre constructeur. Et sifield1
c'est privé, alors les classes héritées ne peuvent pas s'initialiserfield1
.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
BaseClass
méthode et un attribut ajouté par code à partir d'uneSubClass
méthode.Si, comme cela est courant, vous
SubClass
voulez vraiment que tousBaseClass
les invariants de s soient configurés avant de procéder à sa propre personnalisation, alors oui, vous pouvez simplement appelerBaseClass.__init__()
(ou utilisersuper
, 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 leBaseClass.__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 surself
. 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.la source
"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?
la source
Souvent, la sous-classe a des paramètres supplémentaires qui ne peuvent pas être passés à la superclasse.
la source
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 ...
la source
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:
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
la source
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 à ladostuff
place, voudriez-vous toujours que Python appelle automatiquement la fonction correspondante dans la classe parent?la source
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 queB.__init__()
. rendre l'appel explicite signifie qu'un programmeur peut, par exemple, définirB.__init__()
avec des paramètres complètement différents, faire des calculs avec ces données, appelerA.__init__()
avec des arguments appropriés pour cette méthode, puis faire un post-traitement. ce type de flexibilité serait difficile à atteindre s'ilA.__init__()
était appeléB.__init__()
implicitement, soit avant l'B.__init__()
exécution, soit juste après.la source