6.1 L'héritage - c'est simple
L'héritage est un concept fondamental de la programmation orientée objet (OOP) qui permet à une classe (appelée classe fille ou sous-classe) d'hériter des champs et des méthodes d'une autre classe (appelée la classe parente ou super-classe).
Cette approche permet de créer des classes plus générales et de réutiliser le code, améliorant ainsi l'organisation et la maintenabilité du code.
Pourquoi a-t-on besoin de l'héritage ?
Supposons que tu as besoin d'écrire un code et que tu as décidé de le faire sous forme de classe. Puis tu découvres qu'il y a déjà une classe dans ton projet qui fait presque tout ce qu'il te faut. Tu peux simplement copier ce code dans ta classe et l'utiliser à ta guise.
Ou, tu peux "comme copier". Tu peux déclarer cette classe comme parent de la tienne, et Python ajoutera à ta classe le comportement du parent.
Imagine que tu es la nature et que tu veux créer un chien. Qu'est-ce qui serait plus rapide: créer un chien à partir d'une bactérie en un milliard d'années ou domestiquer un loup en 200 000 ans ?
Exemple d'héritage basique
Supposons que tu as une classe parente Animal
avec un champ name :
class Animal:
def __init__(self, name):
self.name = name
Nous voulons créer deux classes filles pour cela — Dog
et Cat
, et leur ajouter à toutes deux la méthode speak
:
class Dog(Animal):
def speak(self):
return f"{self.name} dit Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} dit Meow!"
Dans l'exemple ci-dessus, les classes filles Dog
et Cat
héritent de Animal
et ajoutent la méthode speak
.
Classe Dog
:
- Hérite de l'attribut
name
deAnimal
. - Ajoute une méthode
speak
qui retourne une chaîne spécifique aux chiens.
Classe Cat
:
- Hérite de l'attribut
name
deAnimal
. - Ajoute une méthode
speak
qui retourne une chaîne spécifique aux chats.
La version finale du code ressemble à ceci :
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def speak(self):
return f"{self.name} dit Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} dit Meow!"
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # Affiche: Buddy dit Woof!
print(cat.speak()) # Affiche: Whiskers dit Meow!
Dans cet exemple, Animal
est la classe parente, et Dog
et Cat
sont les classes filles. Les classes filles héritent de l'attribut name
et de la méthode __init__
de la classe parente Animal
, mais ajoutent les méthodes speak
.
6.2 Sous le capot de l'héritage
Si tu as ajouté un parent à ta classe, alors c'est très similaire à comme si tu avais copié le code du parent dans ta classe.
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name):
super().__init__(name) # Appel du constructeur de la classe parente
def speak(self):
return f"{self.name} dit Woof!"
class Cat(Animal):
def __init__(self, name):
super().__init__(name) # Appel du constructeur de la classe parente
def speak(self):
return f"{self.name} dit Meow!"
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # Affiche: Buddy dit Woof!
print(cat.speak()) # Affiche: Whiskers dit Meow!
Ce n'est pas tout à fait exact, mais si tu n'as jamais rencontré le concept d'héritage, tu peux pour l'instant y penser de cette façon. Nous ajouterons plus de détails par la suite.
6.3 Hiérarchie d'héritage
Très souvent, lorsque tu conçois un modèle complexe avec un grand nombre de classes, tu peux rencontrer une hiérarchie complète d'héritage.
Par exemple, tu as une classe Animal
— c'est la classe de base pour tous les animaux :
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement abstract method")
Nous avons même ajouté une méthode speak
, mais comme un animal abstrait ne parle pas, cette méthode lance simplement l'exception NotImplementedError
— c'est une pratique standard.
Ensuite, nous ajoutons des classes intermédiaires qui correspondent aux catégories d'animaux: Mammal
pour les mammifères et Bird
pour les oiseaux.
class Mammal(Animal):
def __init__(self, name, fur_color):
super().__init__(name) # Appel du constructeur de la classe parente
self.fur_color = fur_color
class Bird(Animal):
def __init__(self, name, wing_span):
super().__init__(name) # Appel du constructeur de la classe parente
self.wing_span = wing_span
def fly(self):
return f"{self.name} vole avec une envergure de {self.wing_span} mètres."
Et enfin, seulement à la toute dernière étape apparaissent les classes des espèces animales spécifiques :
class Dog(Mammal):
def speak(self):
return f"{self.name} dit Woof!"
class Cat(Mammal):
def speak(self):
return f"{self.name} dit Meow!"
class Parrot(Bird):
def speak(self):
return f"{self.name} dit Squawk!"
C'est avec eux que le code final fonctionne généralement :
animals = [Dog("Buddy", "brown"), Cat("Whiskers", "white"), Parrot("Polly", 0.5)]
for animal in animals:
print(animal.speak())
print(f"{dog.name} a un pelage {dog.fur_color}.") # Affiche: Buddy a un pelage marron.
print(f"{cat.name} a un pelage {cat.fur_color}.") # Affiche: Whiskers a un pelage blanc.
print(parrot.fly()) # Affiche: Polly vole avec une envergure de 0.5 mètres.
Bien qu'il n'y ait techniquement pas de restrictions sur la création de hiérarchies avec des dizaines d'ancêtres, il est important de se rappeler que sans nécessité, il vaut mieux rester simple. La force réside dans la simplicité.
GO TO FULL VERSION