Prépa Python

Avancé — chapitre 33 sur 54

POO (2/2) : héritage, encapsulation et méthodes spéciales

Héritage, super(), dunders, @property, @dataclass

70 minAbonnés

Le chapitre précédent a posé les bases : classes, instances, attributs, méthodes. Ce chapitre approfondit les 3 grands piliers qui rendent la POO puissante : l'héritage (réutiliser du code), l'encapsulation (protéger les données), et les méthodes spéciales (faire dialoguer nos objets avec Python).

L'héritage

L'héritage permet de créer une nouvelle classe (fille) qui réutilise et étend une classe existante (mère). C'est le mécanisme clé de la réutilisation en POO.

Exemple introductif

Imaginons qu'après avoir créé Personnage, on veuille une classe Guerrier — un personnage spécialisé. Sans héritage, on devrait tout redupliquer. Avec héritage :

python
# Classe mère
class Personnage:
    def __init__(self, nom, pv):
        self.nom = nom
        self.pv = pv

    def attaquer(self, cible):
        print(f"{self.nom} attaque {cible.nom}")

    def __str__(self):
        return f"{self.nom} ({self.pv} PV)"

# Classe fille : Guerrier EST UN Personnage
class Guerrier(Personnage):
    def __init__(self, nom, pv, arme):
        super().__init__(nom, pv)        # appelle le __init__ du parent
        self.arme = arme

    def charger(self, cible):
        print(f"{self.nom} charge {cible.nom} avec son {self.arme} !")

# Utilisation
aragorn = Guerrier("Aragorn", 100, "épée")
legolas = Personnage("Legolas", 80)

aragorn.attaquer(legolas)       # hérité du parent
aragorn.charger(legolas)        # spécifique à Guerrier
print(aragorn)                   # __str__ hérité

Relation conceptuelle : Guerrier est un Personnage. On dit que Guerrier hérite de Personnage, ou qu'il en est une sous-classe.

Syntaxe

python
class ClasseFille(ClasseMere):
    ...

Dans le corps, on peut :

  1. Ajouter de nouveaux attributs et méthodes.
  2. Redéfinir (override) des méthodes existantes.
  3. Appeler les méthodes du parent avec super().

super() — accéder à la classe parente

super() permet d'appeler les méthodes du parent depuis la classe fille. Usage principal : dans __init__, pour initialiser d'abord ce que le parent fait, puis ajouter ses propres initialisations.

python
class Vehicule:
    def __init__(self, marque, annee):
        self.marque = marque
        self.annee = annee

    def presenter(self):
        print(f"Véhicule {self.marque}, {self.annee}")

class Voiture(Vehicule):
    def __init__(self, marque, annee, nb_portes):
        super().__init__(marque, annee)     # init du parent
        self.nb_portes = nb_portes           # spécifique à Voiture

    def presenter(self):
        super().presenter()                   # comportement parent
        print(f"  → {self.nb_portes} portes")   # spécifique ajouté

v = Voiture("Renault", 2022, 5)
v.presenter()

Redéfinir (override) une méthode

On peut remplacer une méthode du parent en la redéfinissant dans la fille.

python
class Animal:
    def cri(self):
        return "..."

class Chien(Animal):
    def cri(self):
        return "Wouf !"

class Chat(Animal):
    def cri(self):
        return "Miaou !"

class Canard(Animal):
    def cri(self):
        return "Coin coin"

# Polymorphisme : même appel, comportements différents
for animal in [Chien(), Chat(), Canard()]:
    print(f"{type(animal).__name__} : {animal.cri()}")

C'est ce qu'on appelle le polymorphisme : même interface (cri()), comportements différents selon le type.

Étendre une méthode (pas juste remplacer)

Parfois, on veut garder le comportement parent et ajouter quelque chose :

python
class Personnage:
    def se_presenter(self):
        print(f"Je suis {self.nom}")

class Heros(Personnage):
    def __init__(self, nom, pouvoir):
        self.nom = nom
        self.pouvoir = pouvoir

    def se_presenter(self):
        super().se_presenter()          # comportement parent d'abord
        print(f"  et j'ai le pouvoir : {self.pouvoir}")

h = Heros("Gandalf", "Magie")
h.se_presenter()

Héritage multi-niveaux

On peut hériter d'une classe qui hérite elle-même d'une autre. La hiérarchie peut être profonde.

python
class Animal:
    def __init__(self, nom):
        self.nom = nom

class Mammifere(Animal):
    def __init__(self, nom, poils):
        super().__init__(nom)
        self.poils = poils

class Chien(Mammifere):
    def __init__(self, nom, race):
        super().__init__(nom, poils=True)
        self.race = race

    def aboyer(self):
        print(f"{self.nom} (race {self.race}) aboie !")

rex = Chien("Rex", "Berger")
rex.aboyer()
print(f"A des poils : {rex.poils}")

isinstance et les hiérarchies

isinstance(obj, Classe) renvoie True pour toutes les classes parentes, pas juste la classe directe.

python
class Animal: pass
class Mammifere(Animal): pass
class Chien(Mammifere): pass

rex = Chien()

print(isinstance(rex, Chien))        # True
print(isinstance(rex, Mammifere))    # True (ancêtre)
print(isinstance(rex, Animal))       # True (ancêtre plus lointain)

C'est le comportement attendu : un Chien est un Mammifere, est un Animal.

Suite réservée aux abonnés

Débloquez l'intégralité du parcours

Les 54 chapitres, les exercices corrigés automatiquement, les examens blancs et le suivi de votre progression.

Voir les offresChapitres en accès libre

Déjà un accès ? Connectez-vous.