Pourquoi les concepteurs Python ont-ils décidé que les méthodes __init__()
des sous-classes n'appellent pas automatiquement les méthodes __init__()
de leurs super-classes, comme dans d'autres langages? Est-ce que le langage Pythonic et recommandé est vraiment comme suit?
class Superclass(object):
def __init__(self):
print 'Do something'
class Subclass(Superclass):
def __init__(self):
super(Subclass, self).__init__()
print 'Do something else'
La distinction cruciale entre le __init__
De Python et ces autres langages constructeurs est que __init__
Est pas un constructeur: c'est un initialiseur (l'actuel constructeur (le cas échéant, mais voir plus tard ;-) est __new__
et fonctionne à nouveau complètement différemment). Bien que construisant toutes les superclasses (et, sans doute, le faire "avant" de continuer à construire vers le bas) fait évidemment partie du fait de dire que vous construisant l'instance d'une sous-classe, c'est clairement Ce n'est pas le cas pour initializing, car il existe de nombreux cas d'utilisation dans lesquels l'initialisation des superclasses doit être ignorée, modifiée, contrôlée - ce qui arrive, voire pas du tout, "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 dans Python pour exactement les mêmes raisons, cette délégation n'est pas automatique pour toute autres méthodes - et notez que ces "autres langages" ne font pas de délégation automatique de super-classes pour toute autre méthode, soit ... , juste pour le constructeur (et, le cas échéant, le destructeur ), ce qui, comme je l’ai dit, est pas ce que __init__
de Python est. (Le comportement de __new__
est également assez particulier, bien que cela ne soit pas directement lié à votre question, puisque __new__
est un constructeur si particulier qu'il n'a pas nécessairement besoin de construire quoi que ce soit - il pourrait très bien renvoyer une instance existante, ou même une non-instance ... clairement Python vous offre un lot plus de contrôle de la mécanique que les "autres langues" que vous avez en tête, qui est également inclut l’absence de délégation automatique dans __new__
même! -).
Je suis un peu gêné quand les gens assimilent le "Zen of Python", comme si c'était une justification. C'est une philosophie de conception. des décisions de conception particulières peuvent toujours être expliquées en termes plus spécifiques - et elles doivent l'être, sinon le "Zen de 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 de manière similaire à la manière dont vous construisez la classe de base. Vous pouvez avoir plus de paramètres, moins, ils peuvent être dans un ordre différent ou non liés du tout.
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")
Ceci s’applique à toutes les méthodes dérivées, pas seulement __init__
.
Java et C++ nécessite qu'un constructeur de classe de base soit appelé en raison de la structure de la mémoire.
Si vous avez une classe BaseClass
avec un membre field1
, Et que vous créez une nouvelle classe SubClass
qui ajoute un membre field2
, Une instance de SubClass
contient des espaces pour field1
et field2
. Vous avez besoin d'un constructeur de BaseClass
pour renseigner field1
, À moins que vous n'obligiez toutes les classes héritées à répéter l'initialisation de BaseClass
dans leurs propres constructeurs. Et si field1
Est privé, alors les classes héritées ne peut pas 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 toute initialisation , toutes les instances de toutes les classes définies par l'utilisateur sont presque exactement les mêmes; ce ne sont que des emplacements pour stocker des attributs qui n'en stockent pas encore.
Il est donc parfaitement logique que la sous-classe Python n’appelle pas son constructeur de classe de base. Elle pourrait simplement ajouter les attributs elle-même si elle le souhaitait. Aucun espace n’est réservé pour un nombre donné de champs pour chaque propriété. classe dans la hiérarchie, et il n'y a pas de différence entre un attribut ajouté par le code d'une méthode BaseClass
et un attribut ajouté par le code d'une méthode SubClass
.
Si, comme il est courant, SubClass
veut réellement que tous les invariants de BaseClass
soient configurés avant de procéder à sa propre personnalisation, alors vous pouvez simplement appeler BaseClass.__init__()
(ou utilisez super
, mais c'est compliqué et a parfois ses propres problèmes). Mais tu n'es pas obligé. Et vous pouvez le faire avant, après ou avec des arguments différents. Si tu voulais, tu pourrais appeler le BaseClass.__init__
Depuis une autre méthode que __init__
; peut-être avez-vous des projets d’initialisation paresseux bizarres.
Python réalise cette flexibilité en gardant les choses simples. Vous initialisez des objets en écrivant une méthode __init__
Qui définit les attributs sur self
. C'est ça. 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 non intuitives sur les choses à faire en premier, ou les choses qui arriveront automatiquement si vous ne faites pas d'autres choses. Son seul objectif est de servir de point d'ancrage lors de l'initialisation de l'objet pour définir les valeurs d'attribut initiales. Si vous voulez qu'il fasse autre chose, vous l'écrivez explicitement dans votre code.
"Explicite est meilleur qu'implicite." C'est le même raisonnement qui indique que nous devrions explicitement écrire "self".
Je pense qu'en fin de compte c'est un avantage-- pouvez-vous réciter toutes les règles Java a en ce qui concerne l'appel des constructeurs des superclasses?
La sous-classe a souvent des paramètres supplémentaires qui ne peuvent pas être transmis à la super-classe.
Actuellement, nous avons une assez longue page décrivant l'ordre de résolution de la méthode 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 pour expliquer l'ordre dans lequel cela se produit. Ce serait l'enfer ...
Pour éviter toute confusion, il est utile de savoir que vous pouvez appeler la méthode base_class __init__()
si la classe child_class n'a pas de classe __init__()
.
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 dans python va rechercher __init__()
] dans la classe parente quand ne peut pas le trouver dans la classe des enfants. Vous devez appeler le constructeur de la classe parente directement si vous ont déjà une méthode __init__()
dans la classe children.
Par exemple, le code suivant retournera une erreur: classe 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.
Peut être __init__
est la méthode que la sous-classe doit remplacer. Parfois, les sous-classes ont besoin de l'exécution de la fonction parent avant d'ajouter du code spécifique à la classe. D'autres fois, elles doivent configurer des variables d'instance avant d'appeler la fonction du parent. Puisqu'il n'y a aucun moyen Python pourrait savoir quand il serait le plus approprié d'appeler ces fonctions, il ne faut pas deviner.
Si cela ne vous influence pas, considérez que __init__
n'est qu'une autre fonction. Si la fonction en question était dostuff
, voudriez-vous toujours Python appeler automatiquement la fonction correspondante dans la classe parente?
je crois que la considération très importante ici est qu'avec un appel automatique à super.__init__()
, vous proscrivez, par conception, lorsque cette méthode d'initialisation est appelée et avec quels arguments. éviter de l'appeler automatiquement et de demander explicitement au programmeur d'effectuer cet appel implique beaucoup de souplesse.
après tout, ce n'est pas parce que la classe B est dérivée de la classe A que A.__init__()
peut ou doit être appelé avec les mêmes arguments que B.__init__()
. rendre l'appel explicite signifie qu'un programmeur peut avoir par ex. define B.__init__()
avec des paramètres complètement différents, effectuez quelques calculs avec ces données, appelez A.__init__()
avec les arguments appropriés pour cette méthode, puis effectuez un post-traitement. Ce type de flexibilité serait difficile à atteindre si A.__init__()
était appelé implicitement de B.__init__()
, soit avant que B.__init__()
ne s'exécute, soit juste après.