Module : L'Héritage
1. Quoi : L'Héritage
L'héritage est l'un des piliers de la programmation orientée objet. C'est un mécanisme qui permet de créer une nouvelle classe (appelée classe enfant, sous-classe ou classe dérivée) à partir d'une classe existante (appelée classe parente, super-classe ou classe de base).
La classe enfant hérite de tous les attributs et de toutes les méthodes de la classe parente. Elle peut ensuite :
- Utiliser les fonctionnalités héritées telles quelles.
- Ajouter de nouveaux attributs et de nouvelles méthodes.
- Redéfinir (ou "surcharger", override) des méthodes de la classe parente pour qu'elles aient un comportement spécifique.
2. Pourquoi : Réutilisation et Spécialisation
- Réutilisation du code (Principe DRY) : Au lieu de copier-coller du code entre des classes similaires, vous placez le code commun dans une classe parente et vous en faites hériter les classes enfants.
- Création de hiérarchies logiques : L'héritage permet de modéliser des relations "est un" (a is-a relationship). Par exemple, un
Chienest unAnimal. UneVoitureElectriqueest uneVoiture. - Spécialisation : Vous partez d'une classe générale et vous créez des versions plus spécifiques qui ajoutent ou modifient des fonctionnalités.
3. Comment : La Syntaxe en Python
A. Créer une classe enfant
Pour faire hériter une classe, on indique le nom de la classe parente entre parenthèses lors de la définition de la classe enfant.
# Classe Parente
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
# Comportement générique
print(f"{self.name} makes a sound.")
# Classe Enfant qui hérite de Animal
class Dog(Animal):
# Pour l'instant, Dog ne fait rien de plus que Animal
pass
# Utilisation
generic_animal = Animal("Creature")
generic_animal.speak() # Creature makes a sound.
my_dog = Dog("Buddy") # Dog hérite du __init__ de Animal
my_dog.speak() # Dog hérite de la méthode speak() -> Buddy makes a sound.
B. Redéfinir une méthode de la classe parente
La classe enfant peut fournir sa propre implémentation d'une méthode qui existe déjà dans la classe parente.
class Cat(Animal):
# On redéfinit la méthode speak() pour la classe Cat
def speak(self):
print(f"{self.name} says: Meow!")
my_cat = Cat("Whiskers")
my_cat.speak() # Affiche "Whiskers says: Meow!" et non le message générique.
C. Ajouter de nouvelles méthodes
La classe enfant peut avoir ses propres méthodes, qui n'existent pas dans la classe parente.
class Dog(Animal):
# Redéfinition
def speak(self):
print(f"{self.name} says: Woof!")
# Nouvelle méthode, spécifique à Dog
def fetch(self, item):
print(f"{self.name} fetches the {item}.")
my_dog = Dog("Rex")
my_dog.speak() # Rex says: Woof!
my_dog.fetch("ball") # Rex fetches the ball.
D. Étendre le constructeur __init__
Souvent, la classe enfant a besoin d'ajouter ses propres attributs en plus de ceux de la classe parente. Pour cela, il faut :
- Redéfinir la méthode
__init__dans la classe enfant. - Appeler explicitement le constructeur de la classe parente pour s'assurer que ses attributs sont bien initialisés. On utilise pour cela
super().__init__().
class ElectricCar(Car): # On suppose qu'on a une classe Car
"""Represents aspects of a car, specific to electric vehicles."""
def __init__(self, make, model, year, battery_size):
"""
Initialize attributes of the parent class.
Then initialize attributes specific to an electric car.
"""
# 1. Appelle le __init__ de la classe parente (Car)
super().__init__(make, model, year)
# 2. Ajoute le nouvel attribut spécifique à ElectricCar
self.battery_size = battery_size # en kWh
# Nouvelle méthode
def describe_battery(self):
"""Print a statement describing the battery size."""
print(f"This car has a {self.battery_size}-kWh battery.")
# Redéfinition d'une méthode existante (si Car avait une méthode fill_gas_tank)
def fill_gas_tank(self):
"""Electric cars don't have gas tanks."""
print("This car doesn't need a gas tank!")
# Utilisation
my_tesla = ElectricCar('tesla', 'model s', 2023, 100)
print(my_tesla.get_descriptive_name()) # Méthode héritée de Car
my_tesla.describe_battery() # Nouvelle méthode
super()est une fonction spéciale qui vous permet d'appeler des méthodes de la classe parente, évitant ainsi d'avoir à la nommer explicitement. C'est la manière moderne et recommandée de le faire.
4. Héritage Multiple
Python autorise une classe à hériter de plusieurs classes parentes.
class A:
def method_a(self):
print("Method from A")
class B:
def method_b(self):
print("Method from B")
class C(A, B): # Hérite de A et de B
pass
instance_c = C()
instance_c.method_a() # Method from A
instance_c.method_b() # Method from B
L'héritage multiple peut être puissant, mais il peut aussi introduire de la complexité, notamment le "problème du diamant" (diamond problem), qui concerne l'ordre dans lequel les méthodes sont résolues (MRO - Method Resolution Order). Il doit être utilisé avec prudence.
Exercice 01 : Hiérarchie de Personnages de Jeu Vidéo
Objectif
Cet exercice a pour but de vous faire pratiquer l'héritage en créant une hiérarchie de classes pour des personnages de jeu vidéo. Vous créerez une classe de base Character et deux classes enfants Warrior et Mage qui en hériteront et la spécialiseront.
Contexte
Dans un jeu, tous les personnages partagent des caractéristiques communes (un nom, des points de vie). Cependant, des classes spécifiques comme les guerriers et les mages ont des compétences et des attributs uniques.
- Character (Parent) :
- Attributs :
name,health(points de vie),level. - Méthodes :
attack(),show_info().
- Attributs :
- Warrior (Enfant) :
- Hérite de
Character. - Attribut supplémentaire :
rage. - Redéfinit
attack()pour utiliser la rage.
- Hérite de
- Mage (Enfant) :
- Hérite de
Character. - Attribut supplémentaire :
mana. - Redéfinit
attack()pour utiliser le mana.
- Hérite de
Énoncé
Partie 1 : La Classe Parente Character
- Créez un nouveau fichier Python nommé
game_characters.py. - Définissez la classe
Character:- Son constructeur
__init__doit prendrenameetlevelcomme arguments. - Il doit initialiser
self.name,self.level, etself.health(initialisez la vie àlevel * 10). - Créez une méthode
attack()qui affiche :f"{self.name} performs a basic attack.". - Créez une méthode
show_info()qui affiche les informations du personnage (nom, niveau, vie).
- Son constructeur
Partie 2 : La Classe Enfant Warrior
- Définissez la classe
Warriorqui hérite deCharacter. - Étendez le constructeur
__init__:- Il doit prendre
nameetlevelen arguments. - Utilisez
super().__init__()pour appeler le constructeur du parent. - Ajoutez un nouvel attribut
self.rage, initialisé à100.
- Il doit prendre
- Redéfinissez la méthode
attack():- Elle doit vérifier si
self.rageest supérieur ou égal à 10. - Si oui, elle affiche
f"{self.name} swings their mighty axe!"et décrémenteself.ragede 10. - Sinon, elle affiche
f"{self.name} is out of rage.".
- Elle doit vérifier si
Partie 3 : La Classe Enfant Mage
- Définissez la classe
Magequi hérite deCharacter. - Étendez le constructeur
__init__:- Il doit prendre
nameetlevelen arguments. - Utilisez
super().__init__(). - Ajoutez un nouvel attribut
self.mana, initialisé à50.
- Il doit prendre
- Redéfinissez la méthode
attack():- Elle doit vérifier si
self.manaest supérieur ou égal à 20. - Si oui, elle affiche
f"{self.name} casts a powerful fireball!"et décrémenteself.manade 20. - Sinon, elle affiche
f"{self.name} is out of mana.".
- Elle doit vérifier si
Partie 4 : Test
- Créez une instance de
Warrioret une instance deMage. - Affichez les informations de chaque personnage.
- Faites-les attaquer plusieurs fois pour voir leurs ressources (rage/mana) diminuer et le message changer lorsqu'ils n'en ont plus assez.
Résultat Attendu
--- Character Info ---
Name: Conan, Level: 5, Health: 50
Name: Gandalf, Level: 6, Health: 60
--- Combat ---
Conan swings their mighty axe!
Gandalf casts a powerful fireball!
Conan swings their mighty axe!
Gandalf casts a powerful fireball!
Gandalf is out of mana.
Cliquez ici pour voir un exemple de code de solution
# game_characters.py
# 1. Classe Parente
class Character:
def __init__(self, name, level):
self.name = name
self.level = level
self.health = level * 10
def attack(self):
print(f"{self.name} performs a basic attack.")
def show_info(self):
print(f"Name: {self.name}, Level: {self.level}, Health: {self.health}")
# 2. Classe Enfant Warrior
class Warrior(Character):
def __init__(self, name, level):
super().__init__(name, level)
self.rage = 100
def attack(self):
if self.rage >= 10:
print(f"{self.name} swings their mighty axe!")
self.rage -= 10
else:
print(f"{self.name} is out of rage.")
# 3. Classe Enfant Mage
class Mage(Character):
def __init__(self, name, level):
super().__init__(name, level)
self.mana = 50
def attack(self):
if self.mana >= 20:
print(f"{self.name} casts a powerful fireball!")
self.mana -= 20
else:
print(f"{self.name} is out of mana.")
# 4. Test
warrior = Warrior("Conan", 5)
mage = Mage("Gandalf", 6)
print("--- Character Info ---")
warrior.show_info()
mage.show_info()
print("\n--- Combat ---")
warrior.attack()
mage.attack()
warrior.attack()
mage.attack()
mage.attack() # Le mage ne devrait plus avoir assez de mana