Chapitre 16 - Héritage ====================== Rappel - composition --------------------- Dans un chapitre précédent on a parlé de *composition* qui décrit une classe à l'intérieur d'une autre classe. Pour rappel:: class Chat: def __init__(self, nom): self.nom = nom def ronronne(self): print(self.nom, 'fait: "prrrrr"') def caresse(self): self.ronronne() class Enfant: def __init__(self, prénom, chat): self.chat = chat def console(self): self.chat.caresse() Vocabulaire ----------- Ici on va parler d'héritage, qui décrit une autre relation entre classes, appelée parfois un peu abusivement "partage de code". Pour indiquer qu'une classe ``B`` hérite d'une classe ``A``, on écrit ``A`` dans des parenthèses au moment de déclarer la classe ``B``:: class A: ... class B(A): ... Les trois formulations suivantes sont souvent employées: * A est la classe *parente* de B. * B *hérite* de A. * B est une classe *fille* de A. Utilisation ----------- Si une méthode n'est pas trouvée dans la classe courante, Python ira la chercher dans la classe parente:: class A: def méthode_dans_a(self): print("dans A") class B(A): def méthode_dans_b(self): print("dans B") b = B() b.méthode_dans_b() # Affiche: 'dans B', comme d'habitude b.méthode_dans_a() # Affiche: 'dans A' Ordre de résolution -------------------- S'il y a plusieurs classes parentes, Python les remonte toutes une à une. On dit aussi qu'il y a une *hiérarchie* de classes:: class A: def méthode_dans_a(self): print("dans A") class B(A): def méthode_dans_b(self): print("dans B") class C(B): def méthode_dans_c(self): print("dans C") c = C() c.méthode_dans_a() # affiche: 'dans A' Avec \_\_init\_\_ -------------------- La résolution fonctionne pour toutes les méthodes, y compris ``__init__``:: class A: def __init__(self): print("initialisation de A") class B(A): ... b = B() # affiche: "initialisation de A" Attributs ---------- Même mécanisme pour les attributs:: class A: def __init__(self): self.attribut_de_a = 42 class B(A): ... b = B() print(b.attribut_de_a) # affiche: 42 Surcharge ---------- On peut aussi *surcharger* la méthode de la classe parente dans la classe fille:: class A: def une_méthode(self): print("je viens de la classe A") class B(A): def une_méthode(self): print("je viens de la classe B") b = B() b.une_méthode() # affiche: "je viens de la classe B' super() ------- On peut utiliser ``super()`` pour chercher *explicitement* une méthode dans la classe parente:: class A: def une_méthode(self): print("je viens de la classe A") class B(A): def une_méthode(self): super().une_méthode() print("je viens de la classe B") b = B() b.une_méthode() # affiche: # je viens de la classe A # je viens de la classe B super() et \_\_init\_\_ ------------------------ Erreur très courante:: class A: def __init__(self): self.attribut_de_a = "bonjour" class B(A): def __init__(self): self.attribut_de_b = 42 b = B() print(b.attribut_de_b) # affiche: 42 print(b.attribut_de_a) # erreur: AttributeError On a surchargé ``A.__init__()``, du coup l'initialisation de A n'a jamais été faite. La plupart du temps, si ``A`` et ``B`` ont de constructeurs, on appellera ``super().__init__()`` dans le constructeur de la classe fille:: class A: def __init__(self): self.attribut_de_a = "bonjour" class B(A): def __init__(self): super().__init__() self.attribut_de_b = 42 b = B() print(b.attribut_de_b) # affiche: 42 print(b.attribut_de_a) # affiche: "bonjour"