LangageHeritage

[wiki: Héritage] [#license ©]

PageOutline

Généralités

Python permet d’exprimer une relation « est une sorte de » entre deux classes. Si B est une sorte de A, on s’attend à ce que les instances de B possède les attributs (données et méthodes) de cette classe, mais aussi tous ceux de A, puisque ces objets sont aussi une sorte de A. On dit que B hérite de A, ou dérive de A. On peut aussi qualifier A de « classe mère », de « classe de base », de « super-classe », et B de « classe dérivée », de « classe fille », de « sous-classe » de A.

Pour hériter d’une classe mère, il suffit d’indiquer son nom entre parenthèses après le nom de la classe fille. La classe fille a accès à tous les attributs de la classe mère. En d’autres termes, quand on écrit obj.x, l’interpréteur cherche x dans l’instance, puis dans sa classe, puis dans la classe mère de cette classe, etc.

#!div style="width:600px"


{{{
#!python
class A:
def x(self): return "A"
def y(self): return "A"

class B(A):
def z(self): return "B"

b = B()

print(b.x(),b.y(),b.z())


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
A A B
</pre>

}}}

Surcharge

Une classe dérivée peut ajouter de nouveaux attributs, qu’il s’agisse de données ou de méthodes. Elle peut aussi redéfinir un des attributs de la classe de base, auquel cas, pour les instances de la classe dérivée, c’est la nouvelle définition qui sera trouvée la première, et utilisée. On parle de « surcharge ».

#!div style="width:600px"


{{{
#!python
class A:
def x(self): return "A"
def y(self): return "A"
def z(self): return "A"

class B(A):
def x(self): return "B"
def t(self): return "B"

b = B()

print(b.x(),b.y(),b.z(),b.t())


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
B A A B
</pre>

}}}

Héritage multiple

Une classe peut hériter de plusieurs autres en même temps (on donne alors une liste de noms séparés par des virgules). L’ordre de gauche à droite des classes parentes est respecté lorsque l’interpréteur Python recherche un nom (donnée ou méthode).

#!div style="width:600px"


{{{
#!python
class A1:
def x(self): return "A1"

class A2:
def x(self): return "A2"

class B(A1,A2):
pass

b = B()
print(b.x())


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
A1
</pre>

}}}

Arborescence d’héritage

En plus de l’héritage multiple, chaque classe de base peut à son tour hériter d’une autre classe, etc. L’ensemble des classes dont hérite une instance forme ainsi une arborescence parfois complexe. Dans cette arborescence, la recherche d’un attribut se fait de bas en haut et de gauche à droite, en profondeur d’abord, mais en faisant en sorte qu’une classe de base ne soit jamais explorée avant que toutes ses dérivées le soient (on parle de « linéarisation C3 »). Ainsi, dans l’exemple ci-dessous, A est prioritaire sur C, mais pas sur B2 :

#!div style="width:600px"


{{{
#!python
class A:
def x(self): return "A"
def y(self): return "A"
def z(self): return "A"

class B1(A):
pass

class B2(A):
def x(self): return "B2"

class C:
def y(self): return "C"
def z(self): return "C"

class D(B1,B2,C):
z = C.z

d = D()
print(d.x(),d.y(),d.z())


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
B2 A C
</pre>

}}}

L’ordre de parcours des classes est appelé le MRO (Method Resolution Order).

On voit aussi, dans l’exemple ci-dessus, que l’on peut explicitement copier la méthode z de C dans D. Ainsi, tout appel à la méthode z pour une instance de D se trouve redirigé vers la méthode de C (au lieu de A).

Chaque recherche d’attribut est indépendante et repart de self

Lorsque j’appelle une méthode m1 de l’objet obj, cette méthode est recherchée dans l’arborescence de classes de obj. Si, à son tour, m1 appelle une autre méthode m2, la recherche de m2 repart de obj (et pas de la classe de m1). En C++, on dirait que toutes les méthodes sont virtuelles et toutes les variables polymorphiques.

#!div style="width:600px"


{{{
#!python
class A:
def x(self): return "A"
def y(self): return "A"
def affichex(self): print(self.x())

class B1(A):
pass

class B2(A):
def x(self): return "B2"

class C:
def y(self): return "C"
def affichey(self): print(self.y())

class D(B1,B2,C):
pass

d = D()
d.affichex()
d.affichey()


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
B2
A
</pre>

}}}

Si on transforme les méthodes x() et y() en attributs pseudo-privés, ces attributs ne sont visibles qu’aux méthodes de leurs classes respectives, et ces méthodes utiliseront exclusivement ces attributs.

#!div style="width:600px"


{{{
#!python
class A:
def __x(self): return "A"
def __y(self): return "A"
def affichex(self): print(self.__x())

class B1(A):
pass

class B2(A):
def __x(self): return "B2"

class C:
def __y(self): return "C"
def affichey(self): print(self.__y())

class D(B1,B2,C):
pass

d = D()
d.affichex()
d.affichey()


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
A
C
</pre>

}}}

Reutiliser explicitement une méthode de base

Lorsqu’une méthode est redéfinie dans une classe dérivée, on dit qu’elle est surchargée. Plutôt que de réécrire toutes les instructions, il peut être utile de commencer par exécuter le code de la méthode de la classe de base. C’est particulièrement vrai pour les méthodes spéciales, telles que les constructeurs. Il est assez facile d’appeler explicitement une méthode de base en passant par le nom de la classe :

#!div style="width:600px"


{{{
#!python
class Fruit:
def init(self, couleur, variete=''):
self.__couleur= couleur
self.__variete= variete
def str(self):
if (self.__variete!=''):
return "{:s} {:s}".format(self.variete,self.couleur)
else:
return "fruit {:s}".format(self.__couleur)

class Pomme(Fruit):
def init(self, couleur, variete='golden'):
Fruit.init(self,couleur,variete)
def str(self):
return ("pomme " + Fruit.str(self))

p = Pomme('rouge')

print(p)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
pomme golden rouge
</pre>

}}}

On peut remplacer l’appel explicite à la classe de base par un appel à la fonction prédéfinie super(). Cela permet de modifier ultérieurement une classe de base sans avoir à corriger tous les appels directs aux méthodes de cette classe mère. Dans cette variante, on ne redonne pas self en premier argument de l’appel de fonction.

#!div style="width:600px"


{{{
#!python
class Pomme(Fruit):
def init(self, couleur, variete='golden'):
super().init(couleur,variete)
def str(self):
return ("pomme " + super().str())

p = Pomme('rouge')

print(p)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
pomme golden rouge
</pre>

}}}

En cas d’héritage multiple, super() retourne le parent le plus proche en suivant le chemin habituellement parcouru pour rechercher un attribut (MRO). Ainsi, si les méthodes de même nom s’appellent en chaîne à travers super(), on parcourt l’ensemble de l’arborescence.

#!div style="width:600px"


{{{
#!python
class A:
def x(self):
print("<A>")
print("</A>")

class B1(A):
def x(self):
print("<B1>")
super().x()
print("</B1>")

class B2(A):
def x(self):
print("<B2>")
super().x()
print("</B2>")

class C(B1,B2):
def x(self):
print("<C>")
super().x()
print("</C>")

c = C()
c.x()


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
<C>
<B1>
<B2>
<A>
</A>
</B2>
</B1>
</C>
</pre>

}}}

Travaux Pratiques

1. Ajoutez dans calculatrice.py une classe dérivée CalculatriceEtendue, qui ajoute la possibilité de demander l’inverse du dernier résultat obtenu, en tapant inv (et rien que ça), ou bien sa racine carrée en tapant sqrt. Traiter ces cas particuliers dans la méthode call de la nouvelle classe CalculatriceEtendue (pas dans la classe Expression).
1. Dans main.py, utilisez et testez la nouvelle calculatrice.

© David Chamont, Laboratoire Leprince-Ringuet (LLR) / IN2P3 / CNRS, Ecole polytechnique / Université Paris-Saclaybr
© Ce(tte) œuvre est mise à disposition selon les termes de la Licence Creative Commons Attribution – Partage dans les Mêmes Conditions 4.0 International [=#license]


Attachments