Date de première publication : 2022/10/4
L'objectif de ce petit TP est de vous montrer l'héritage en Python.
Héritage simple "usuel"
Écrivons notre premier héritage simple : la classe Fille
hérite de la classe Mere
:
class Mere:
def __init__(self, nom):
self.nom = nom
print("constructeur mere")
def print(self):
print("mere ", self.nom)
class Fille(Mere):
pass
fille = Fille("coucou")
fille.print()
Que peut-on en déduire ?
- Ça marche !
- le constructeur et la méthode
print()
sont hérités
Si maintenant, on essaie :
class Fille(Mere):
def __init__(self):
print("constructeur fille")
fille = Fille()
fille.print()
Qu'obtient-on ?
Un objet Fille
est construit mais ni le constructeur de la classe mère ni l'attribut nom
ne sont connus !!!
Il n'y a pas d'appel implicite au constructeur de la classe mère. Il faut donc le faire de manière explicite.Deux syntaxes possibles (une vient du python 2 - celle en commentaire) existent:
class Fille(Mere):
def __init__(self, nom):
super().__init__(nom)
# Mere.__init__(self, nom)
print("constructeur fille")
Testons la redéfinition de méthode:
class Fille(Mere):
def __init__(self, nom):
super().__init__(nom)
print("constructeur fille")
def print(self):
print("fille ", self.nom)
fille = Fille("loulou")
fille.print()
Vous avez deviné la manière d'appeler la méthode print()
de la classe mère à partir de la classe fille ?
class Fille(Mere):
# ...
def print(self):
super().print()
print("fille ", self.nom)
La classe Fille peut avoir des attributs que la classe Mere n'a pas :
class Fille(Mere):
def __init__(self, nom):
super().__init__("aucun")
self.nom2 = nom
print("constructeur fille")
def print(self):
super().print()
print("verif ", self.nom)
print("fille ", self.nom2)
fille = Fille("encore")
fille.print()
Cela peut engendrer des trucs bizarres :
class Mere:
def __init__(self, nom):
self.nom = nom
print("constructeur mere")
def print(self):
print("mere ", self.nom)
print("WTF ", self.nom2)
Cela marche évidemment si l'objet connait l'attribut (cela marche pour un objet fille mais pas pour un objet mère seulement)
En UML, on peut définir un attribut dans une classe mère et une classe fille peut également définir un attribut avec le même nom. Cela signifie que la classe fille bénéficie de deux attributs : un propre et l'autre par héritage... Peut-on avoir cette situation en Python ?
class Fille(Mere):
def __init__(self, nom):
super().__init__("aucun")
self.nom = nom
print("constructeur fille")
def print(self):
super().print()
print("fille ", self.nom)
fille = Fille("eee")
fille.print()
La réponse est donc NON même s'il n'y a pas de message d'erreur !!! C'est une belle différence avec d'autres langages objets comme le Java ou le C++.
class Mere:
def __init__(self, nom):
self.__nom = nom
print("constructeur mere avec att prive")
def print(self):
print("** mere privee", self.__nom)
class Fille(Mere):
#def __init__(self, nom):
# super().__init__(nom)
def print(self):
print("** fille private ", self.__nom)
fille = Fille("privacy")
fille.print()
Le comportement est celui auquel on s'attend : l'attribut privé n'est pas accessible dans la classe fille mais je suis sûr que vous avez trouvé comme y avoir accès, n'est-ce pas ?
Si l'attribut n'avait pas été précédé de deux underscores mais d'un seul, l'attribut eut été accessible dans la classe fille, mais pas officiellement de l'extérieur. C'est pour cela que parfois, on dit que les attributs avec un seul underscore sont "protégés".
Héritage des membres de classes
On peut vérifier que les attributs de classes et méthodes de classes sont hérités : attributs et méthodes sont accessibles par les classes filles si la visibilité est correcte.
Si un attribut de classe de la classe fille porte le même nom qu'un attribut de classe de la classe mère, il y a masquage.
Héritage multiple
Lors de l'héritage multiple, le python utilise un algorithme de levée d'ambiguité, appelé C3, et dont le résultat est consultable par la méthode mro()
ou __mro__
pour method resolution order (il est conseillé de passer par l'attribut)
class A1:
def print(self):
print("A1")
class A2:
def print(self):
print("A2")
class A3:
def print(self):
print("A3")
class B1(A1,A2):
def print(self):
print("B1")
class B3(A3):
def print(self):
print("B3")
class C1(B1,B3):
def print(self):
print("C1")
c1 = C1()
c1.print()
C1.mro()
C1.__mro__
Vous pouvez mettre en commentaire les méthodes print()
pour voir ce que cela donne...
Dans un exemple comme celui-ci, cela ne résoud pas tout :
class X:
def print(self):
print("X")
class Y:
def print(self):
print("Y")
class A(X, Y):
def print(self):
print("A")
class B(Y, X):
def print(self):
print("B")
class F(A, B):
def print(self):
print("F")