Les classes abstraites sont des classes qui ne peuvent pas être instanciées, elles contiennent une ou plusieurs méthodes abstraites. C’est un modèle pour d’autres classes qui héritent un ensemble de méthodes et de propriétés.

Une méthode abstraite est une méthode déclarée, mais qui n’a pas d’implémentation. Toutefois, une classe abstraite nécessite des sous-classes qui fournissent des implémentations pour les méthodes abstraites.
L’abstraction est très utile lors de la conception de systèmes complexes pour limiter la répétition et assurer la cohérence. C’est similaire à une interface dans d’autres langages.
De plus, les classes abstraites nous donnent est une manière standard de développer le code même si vous avez plusieurs développeurs travaillant sur un projet.
Le module abc de Python fournit les fonctionnalités pour définir et utiliser des classes abstraites.
Un exemple d’une classe abstraite en Python
Tout d’abord, commençons par définir trois classes Lion, Panda et Singe :
class Lion:
def nourrir(self):
print("Nourrir le lion avec de la viande crue!")
class Panda:
def nourrir_animal(self):
print("Nourrir le panda avec du bambou!")
class Singe:
def nourriture(self):
print("Nourrir le singe avec des bananes!")
Notre travail consiste à nourrir tous les animaux en utilisant un script Python.
# Les animaux du zoo
leo = Lion()
po = Panda()
tok = Singe()
# Nourrir les animaux du zoo
leo.nourrir()
po.nourrir_animal()
tok.nourriture()
Le résultat du code.
Nourrir le lion avec de la viande crue!
Nourrir le panda avec du bambou!
Nourrir le singe avec des bananes!
Mais imaginez combien de temps cela prendrait pour chaque animal d’un grand zoo, en répétant le même processus et en codant des centaines de fois. Cela rendrait également le code plus difficile à maintenir.
En fait, afin d’optimiser le processus, la structure de notre programme pourrait ressembler à ceci :
# mettre les animaux dans une liste
zoo = [leo, po, tok]
# nourrir les animaux à travers une boucle
for animal in zoo:
# Mais quelle méthode à utiliser ?
# nourrir(), nourrir_animal ou nourriture?
# Nous aurons : an AttributeError!
Le problème est que chaque classe a un nom de méthode différent, quand on nourrit un lion, c’est la méthode nourrir()
, quand on nourrit un panda, c’est nourrir_animal()
et c’est nourriture()
pour le singe.
Ce code est un gâchis, car les méthodes qui font la même chose doivent avoir le même nom.
Si nous pouvions seulement forcer nos classes à implémenter les mêmes noms de méthodes.
Il s’avère que la classe abstraite est ce dont nous avons besoin. Puisque’elle force ses sous-classes à implémenter toutes ses méthodes abstraites.
En créant une classe abstraite Animal
, chaque classe enfant qui hérite de la classe Animal
doit implémenter des méthodes abstraites de Animal
, qui dans notre cas est la méthode nourrir()
.
from abc import ABC, abstractmethod
# abc est un module python intégré, nous importons ABC et abstractmethod
class Animal(ABC): # hériter de ABC(Abstract base class)
@abstractmethod # un décorateur pour définir une méthode abstraite
def nourrir(self):
pass
Nous avons importé le module intégré abc
. Lors de la définition d’une classe abstraite, nous avons hérité de la classe abstraite de base – ABC
. Pour définir une méthode abstraite dans la classe abstraite, nous avons utilisé un décorateur @abstractmethod
.
Si vous héritez de la classe Animal
, mais n’implémentez pas les méthodes abstraites, vous obtiendrez une erreur.
Si nous essayons d’instancier la classe, cela générera un TypeError
, car nous ne pouvons pas instancier Panda
sans implémenter une méthode abstraite nourrir()
.
class Panda(Animal):
def autre_nom(self):
print("Nourrir le panda avec du bamboo!")
po = Panda()
TypeError: Can't instantiate abstract class Panda with abstract methods nourrir
Donc, voici ce qu’il faudrait faire.
class Lion(Animal):
def nourrir(self):
print("Nourrir le lion avec de la viande crue!")
class Panda(Animal):
def nourrir(self):
print("Nourrir le panda avec du bambou!")
class Singe(Animal):
def nourrir(self):
print("Nourrir le singe avec des bananes!")
Puis, voici le code dont nous avons besoin pour créer et nourrir nos animaux.
zoo = [Lion(), Panda(), Singe()]
for animal in zoo:
animal.nourrir()
Des méthodes abstraites avec des paramètres
Lorsque la sous-classe implémente une méthode, elle doit également contenir tous les paramètres de la classe de base. L’implémentation de la sous-classe peut également ajouter des paramètres supplémentaires si nécessaire.
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def faire_quelque_chose(self, action):
pass
class Lion(Animal):
def faire_quelque_chose(self, action, time):
print(f"{action} le lion à {time}")
class Panda(Animal):
def faire_quelque_chose(self, action, time):
print(f"{action} le panda à {time}")
class Singe(Animal):
def faire_quelque_chose(self, action, time):
print(f"{action} le singe à {time}")
La méthode faire_quelque_chose
possède un paramètre action
. De plus, time
est un autre paramètre des sub-classes.
zoo = [Lion(), Panda(), Singe()]
for animal in zoo:
animal.faire_quelque_chose(action="nourrir", time="10h00")
L’exécution du code.
nourrir le lion à 10h00
nourrir le panda à 10h00
nourrir le singe à 10h00
Nous pouvons également utiliser des arguments par défaut.
Créer des propriétés abstraites – @property
Nous avons vu comment utiliser le décorateur @property.
Si nous voulons créer des propriétés abstraites et forcer notre sous-classe à implémenter ces propriétés en utilisant les décorateurs @property
et @absctractmethod
.
Comme les animaux ont souvent des régimes alimentaires différents, nous allons définir une méthode diet
dans nos classes d’animaux. Puisque tous les animaux héritent de Animal
, nous pouvons définir diet
comme étant une propriété abstraite. De plus, nous aurons besoin d’une autre méthode aliment
qui définit les aliments que nous donnerons aux animaux et qui sera une propriété. Et nous aurons un setter
de aliment
vérifiera si nous essayons de nourrir l’animal avec quelque chose qui n’est pas dans diet
.
from abc import ABC, abstractmethod
class Animal(ABC):
@property
def alimentation(self):
return self._aliment
@alimentation.setter
def alimentation(self, aliment):
if aliment in self.diet:
self._aliment = aliment
else:
raise ValueError(f"Cet animal ne mange de {faliment}.")
@property
@abstractmethod
def diet(self):
pass
@abstractmethod
def nourrir(self, time):
pass
class Lion(Animal):
@property
def diet(self):
return ["cheval", "gazelle", "buffle"]
def nourrir(self, time):
print(f"Le lion mange de la viande de {self._aliment} à {time}")
class Serpent(Animal):
@property
def diet(self):
return ["grenouille", "lapin"]
def nourrir(self, time):
print(f"Le serpent mange de la viande de {self._aliment} à {time}")
Nous allons créer deux objets, définir la nourriture des animaux, puis appeler la méthode nourrir()
.
leo = Lion()
leo.alimentation = "buffle"
leo.nourrir("12h00")
jo = Serpent()
jo.alimentation = "grenouille"
jo.nourrir("12h20")
L’exécution du code.
Le lion mange de la viande de buffle à 12h00
Le serpent mange de la viande de grenouille à 12h20
Si nous essayons de nourrir un animal avec quelque chose qu’il ne mange pas.
leo = Lion()
leo.alimentation = "carottes"
leo.nourrir("12h00")
ValueError: Cet animal ne mange de carottes.
En résumé :
- Les classes enfants doivent implémenter les méthodes et les propriétés définies dans la classe abstraite de base.
- La classe abstraite de base ne peut pas être instanciée.
- Nous utilisons
@abstractmethod
pour définir une méthode dans la classe abstraite et une combinaison de@property
et@abstractmethod
afin de définir une propriété abstraite.
vraiment bien fait et bien commenté
….. merci ! Je prépare mon entretien xD.
Bonne chance !