
Classes abstraites et interfaces (abc module)
Découvrez les classes abstraites et les interfaces en Python, et comment les utiliser avec le module 'abc' (Abstract Base Classes). Définissez des contrats clairs pour vos classes et assurez-vous que les sous-classes implémentent les méthodes requises.
Qu'est-ce qu'une classe abstraite ? Un modèle incomplet
Une classe abstraite est une classe qui ne peut pas être instanciée directement. Elle sert de modèle (ou de "blueprint") pour d'autres classes (ses sous-classes).
Une classe abstraite peut contenir :
- Des méthodes abstraites : Des méthodes qui sont déclarées mais qui n'ont pas d'implémentation. Les sous-classes *doivent* fournir une implémentation pour ces méthodes.
- Des méthodes concrètes : Des méthodes avec une implémentation complète, qui peuvent être héritées ou redéfinies par les sous-classes.
- Des attributs.
Les classes abstraites permettent de définir une interface commune pour un ensemble de classes, sans imposer une implémentation spécifique. Elles forcent les sous-classes à implémenter certaines méthodes, garantissant ainsi un certain niveau de conformité.
Qu'est-ce qu'une interface ? Un contrat à respecter
Une interface est un ensemble de méthodes (et éventuellement d'attributs) qu'une classe doit implémenter.
En Python, il n'y a pas de mot-clé spécifique pour définir une interface comme dans d'autres langages (Java, par exemple). Les interfaces sont généralement implémentées en utilisant des classes abstraites.
Une interface définit un *contrat* que les classes qui l'implémentent doivent respecter. Ce contrat spécifie quelles méthodes doivent être présentes, mais ne dit rien sur la manière dont ces méthodes doivent être implémentées.
Les interfaces permettent de découpler le code qui utilise une classe de l'implémentation concrète de cette classe. Cela favorise la flexibilité, la maintenabilité et la réutilisabilité du code.
Le module abc : Abstract Base Classes
Le module `abc` (Abstract Base Classes) de la bibliothèque standard Python fournit les outils pour définir des classes abstraites et des interfaces.
Les éléments clés du module `abc` sont :
- `ABC` : Une classe de base (mixin) que vous pouvez utiliser pour créer des classes abstraites.
- `abstractmethod` : Un décorateur que vous utilisez pour déclarer une méthode comme étant abstraite.
Pour créer une classe abstraite, vous devez hériter de `ABC` (ou d'une autre classe abstraite) et utiliser le décorateur `@abstractmethod` pour marquer les méthodes abstraites.
Exemple :
from abc import ABC, abstractmethod
class Forme(ABC):
@abstractmethod
def aire(self):
pass # Pas d'implémentation dans la classe abstraite
@abstractmethod
def perimetre(self):
pass
class Cercle(Forme):
def __init__(self, rayon):
self.rayon = rayon
def aire(self):
return 3.14159 * self.rayon * self.rayon
def perimetre(self):
return 2 * 3.14159 * self.rayon
# f = Forme() # TypeError: Can't instantiate abstract class Forme with abstract methods aire, perimetre
c = Cercle(5)
print(c.aire())
print(c.perimetre())Dans cet exemple :
- `Forme` est une classe abstraite (elle hérite de `ABC` et a des méthodes abstraites).
- `aire` et `perimetre` sont des méthodes abstraites (décorées avec `@abstractmethod`). Elles n'ont pas d'implémentation dans la classe `Forme`.
- `Cercle` est une classe concrète qui hérite de `Forme`. Elle *doit* implémenter les méthodes abstraites `aire` et `perimetre`.
- Vous ne pouvez pas créer une instance de `Forme` directement (cela lèverait une `TypeError`). Vous devez créer une instance d'une sous-classe concrète (comme `Cercle`).
Pourquoi utiliser des classes abstraites et des interfaces ?
Les classes abstraites et les interfaces sont utiles pour :
- Définir un contrat : Elles permettent de définir un ensemble de méthodes que les sous-classes doivent implémenter. Cela garantit que les sous-classes auront un comportement cohérent.
- Forcer l'implémentation de méthodes : Si une sous-classe n'implémente pas toutes les méthodes abstraites de sa classe mère abstraite, Python lèvera une erreur lors de la création d'une instance de la sous-classe. Cela permet de détecter les erreurs plus tôt dans le cycle de développement.
- Faciliter le polymorphisme : Vous pouvez écrire du code qui fonctionne avec des objets de différentes classes, tant qu'ils respectent l'interface définie par la classe abstraite.
- Améliorer la lisibilité et la maintenabilité du code : Les classes abstraites et les interfaces rendent le code plus clair et plus facile à comprendre, en explicitant les relations entre les classes.
Les classes abstraites et interfaces sont particulierement utiles dans des projets de grande envergure ou des projets avec plusieurs developpeurs.
Différence entre classe abstraite et interface (en Python)
En Python, la distinction entre une classe abstraite et une interface est subtile, car il n'y a pas de mot-clé `interface`.
- Classe abstraite : Peut contenir à la fois des méthodes abstraites et des méthodes concrètes. Peut avoir des attributs.
- Interface (en Python, implémentée avec une classe abstraite) : Ne devrait contenir *que* des méthodes abstraites (et éventuellement des constantes, mais pas d'attributs d'instance). Elle définit *uniquement* le contrat, pas l'implémentation.
En pratique, en Python, on utilise souvent le terme "classe abstraite" pour désigner à la fois les classes abstraites et les interfaces.
L'important est de comprendre le concept : une interface définit un ensemble de méthodes que les classes doivent implémenter, sans imposer de détails d'implémentation.
Exemple concret : interface pour des formes géométriques
Voici un exemple plus complet d'utilisation de classes abstraites pour définir une interface pour des formes géométriques :
from abc import ABC, abstractmethod
import math
class Forme(ABC):
@abstractmethod
def aire(self):
pass
@abstractmethod
def perimetre(self):
pass
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)
# Utilisation polymorphique
formes = [Cercle(5), Rectangle(4, 6), Cercle(2), Rectangle(3,3)]
for forme in formes:
print(f"Aire : {forme.aire()}, Périmètre : {forme.perimetre()}")Dans cet exemple, `Forme` est une classe abstraite qui définit l'interface pour toutes les formes géométriques. `Cercle` et `Rectangle` sont des classes concrètes qui héritent de `Forme` et implémentent les méthodes abstraites `aire` et `perimetre`. La boucle `for` à la fin montre comment on peut utiliser des objets de différentes classes de manière uniforme (polymorphisme), grâce à l'interface commune définie par la classe abstraite `Forme`.