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