logo oujood
🔍

Les classes abstraites en Python (ABC)

Une classe abstraite définit une interface que ses sous-classes doivent respecter. Python les implémente avec le module abc. Ce cours vous montre comment et pourquoi les utiliser.

OUJOOD.COM

Imaginez que vous concevez un système de paiement : carte bancaire, virement, PayPal. Chaque méthode fonctionne différemment, mais toutes doivent pouvoir effectuer un paiement et afficher un reçu. Comment garantir que chaque développeur qui ajoute une nouvelle méthode de paiement n'oublie pas d'implémenter ces deux fonctionnalités ?

C'est exactement le problème que résolvent les classes abstraites. Une classe abstraite définit un contrat : elle liste les méthodes que toute sous-classe doit obligatoirement implémenter. Si une sous-classe oublie une méthode, Python lève une erreur à l'instanciation — avant même que le code s'exécute. Python implémente ce mécanisme via le module abc (Abstract Base Classes).

Créer une classe abstraite avec ABC

Pour créer une classe abstraite, héritez de ABC et décorez les méthodes obligatoires avec @abstractmethod. Une classe abstraite ne peut pas être instanciée directement.

  📋 Copier le code

from abc import ABC, abstractmethod

class Forme(ABC):
    @abstractmethod
    def aire(self):
        pass

    @abstractmethod
    def perimetre(self):
        pass

# Impossible d'instancier une classe abstraite
try:
    f = Forme()
except TypeError as e:
    print(e)
# Résultat : Can't instantiate abstract class Forme
# with abstract methods aire, perimetre

Implémenter les méthodes abstraites dans les sous-classes

Chaque sous-classe concrète doit implémenter toutes les méthodes abstraites. Si elle en oublie une, Python refuse de l'instancier.

  📋 Copier le code

from abc import ABC, abstractmethod
import math

class Forme(ABC):
    @abstractmethod
    def aire(self):
        pass

    @abstractmethod
    def perimetre(self):
        pass

    def description(self):
        # Méthode non abstraite : partagée par toutes les sous-classes
        return f"Aire : {self.aire():.2f}, Périmètre : {self.perimetre():.2f}"

class Cercle(Forme):
    def __init__(self, rayon):
        self.rayon = rayon

    def aire(self):
        return math.pi * self.rayon ** 2

    def perimetre(self):
        return 2 * math.pi * self.rayon

class Rectangle(Forme):
    def __init__(self, largeur, hauteur):
        self.largeur = largeur
        self.hauteur = hauteur

    def aire(self):
        return self.largeur * self.hauteur

    def perimetre(self):
        return 2 * (self.largeur + self.hauteur)

c = Cercle(5)
print(c.description())
# Résultat : Aire : 78.54, Périmètre : 31.42

r = Rectangle(4, 6)
print(r.description())
# Résultat : Aire : 24.00, Périmètre : 20.00

Notez que description() est une méthode normale dans la classe abstraite — elle est héritée par toutes les sous-classes. Les classes abstraites peuvent mélanger méthodes abstraites et méthodes concrètes.

Erreur si une méthode abstraite est oubliée

Si une sous-classe n'implémente pas toutes les méthodes abstraites, Python l'empêche d'être instanciée.

  📋 Copier le code

from abc import ABC, abstractmethod

class Forme(ABC):
    @abstractmethod
    def aire(self):
        pass

    @abstractmethod
    def perimetre(self):
        pass

# Triangle oublie perimetre() → Python refuse de l'instancier
class Triangle(Forme):
    def __init__(self, base, hauteur):
        self.base = base
        self.hauteur = hauteur

    def aire(self):
        return 0.5 * self.base * self.hauteur

    # perimetre() non implémentée !

try:
    t = Triangle(3, 4)
except TypeError as e:
    print(e)
# Résultat : Can't instantiate abstract class Triangle
# with abstract method perimetre

Exemple concret : système de paiement

Un exemple plus proche d'un projet réel : une classe abstraite ModePaiement qui force chaque implémentation à définir comment effectuer un paiement et comment rembourser.

  📋 Copier le code

from abc import ABC, abstractmethod

class ModePaiement(ABC):
    @abstractmethod
    def payer(self, montant):
        pass

    @abstractmethod
    def rembourser(self, montant):
        pass

    def afficher_recu(self, montant):
        # Méthode partagée par tous les modes de paiement
        print(f"Reçu : {montant}€ via {self.__class__.__name__}")

class CarteBancaire(ModePaiement):
    def __init__(self, numero):
        self.numero = numero

    def payer(self, montant):
        print(f"Paiement de {montant}€ par carte se terminant par {self.numero[-4:]}")
        self.afficher_recu(montant)

    def rembourser(self, montant):
        print(f"Remboursement de {montant}€ sur la carte {self.numero[-4:]}")

class PayPal(ModePaiement):
    def __init__(self, email):
        self.email = email

    def payer(self, montant):
        print(f"Paiement de {montant}€ via PayPal ({self.email})")
        self.afficher_recu(montant)

    def rembourser(self, montant):
        print(f"Remboursement de {montant}€ sur le compte PayPal {self.email}")

# Le code appelant traite les deux types de la même façon
paiements = [
    CarteBancaire("1234567890123456"),
    PayPal("alice@exemple.com")
]

for mode in paiements:
    mode.payer(49.99)

Propriété abstraite avec @property et @abstractmethod

On peut combiner @property et @abstractmethod pour forcer les sous-classes à implémenter un attribut calculé.

  📋 Copier le code

from abc import ABC, abstractmethod

class Animal(ABC):
    @property
    @abstractmethod
    def son(self):
        pass

    def parler(self):
        print(f"L\'animal fait : {self.son}")

class Chien(Animal):
    @property
    def son(self):
        return "Woof !"

class Chat(Animal):
    @property
    def son(self):
        return "Miaou !"

chien = Chien()
chien.parler()
# Résultat : L'animal fait : Woof !

chat = Chat()
chat.parler()
# Résultat : L'animal fait : Miaou !

Vérifier si une classe est abstraite ou concrète

  📋 Copier le code

from abc import ABC, abstractmethod
import inspect

class Forme(ABC):
    @abstractmethod
    def aire(self):
        pass

class Cercle(Forme):
    def aire(self):
        return 3.14 * 5 ** 2

# Vérifier si une classe est abstraite
print(inspect.isabstract(Forme))
# Résultat : True

print(inspect.isabstract(Cercle))
# Résultat : False

# Vérifier l'appartenance
print(isinstance(Cercle(), Forme))
# Résultat : True

Par carabde | Mis à jour le 21 avril 2026