Skip to main content
Niveau : Avancé

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 Chien est un Animal. Une VoitureElectrique est une Voiture.
  • 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 :

  1. Redéfinir la méthode __init__ dans la classe enfant.
  2. 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().
  • Warrior (Enfant) :
    • Hérite de Character.
    • Attribut supplémentaire : rage.
    • Redéfinit attack() pour utiliser la rage.
  • Mage (Enfant) :
    • Hérite de Character.
    • Attribut supplémentaire : mana.
    • Redéfinit attack() pour utiliser le mana.

Énoncé

Partie 1 : La Classe Parente Character

  1. Créez un nouveau fichier Python nommé game_characters.py.
  2. Définissez la classe Character :
    • Son constructeur __init__ doit prendre name et level comme arguments.
    • Il doit initialiser self.name, self.level, et self.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).

Partie 2 : La Classe Enfant Warrior

  1. Définissez la classe Warrior qui hérite de Character.
  2. Étendez le constructeur __init__ :
    • Il doit prendre name et level en arguments.
    • Utilisez super().__init__() pour appeler le constructeur du parent.
    • Ajoutez un nouvel attribut self.rage, initialisé à 100.
  3. Redéfinissez la méthode attack() :
    • Elle doit vérifier si self.rage est supérieur ou égal à 10.
    • Si oui, elle affiche f"{self.name} swings their mighty axe!" et décrémente self.rage de 10.
    • Sinon, elle affiche f"{self.name} is out of rage.".

Partie 3 : La Classe Enfant Mage

  1. Définissez la classe Mage qui hérite de Character.
  2. Étendez le constructeur __init__ :
    • Il doit prendre name et level en arguments.
    • Utilisez super().__init__().
    • Ajoutez un nouvel attribut self.mana, initialisé à 50.
  3. Redéfinissez la méthode attack() :
    • Elle doit vérifier si self.mana est supérieur ou égal à 20.
    • Si oui, elle affiche f"{self.name} casts a powerful fireball!" et décrémente self.mana de 20.
    • Sinon, elle affiche f"{self.name} is out of mana.".

Partie 4 : Test

  1. Créez une instance de Warrior et une instance de Mage.
  2. Affichez les informations de chaque personnage.
  3. 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