web-dev-qa-db-fra.com

Pourquoi les méthodes super-classes __init__ ne sont-elles pas automatiquement appelées?

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'
143
jrdioko

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! -).

157
Alex Martelli

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__.

36
Glenn Maynard

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.

18
Ben

"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?

10
Mike Axiak

La sous-classe a souvent des paramètres supplémentaires qui ne peuvent pas être transmis à la super-classe.

8
John La Rooy

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 ...

7
viraptor

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.
4
Keyvanrm

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?

3
Kirk Strauser

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.

2
flow