Les classes abstraites en Python pour un code plus propre

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.

classes abstraites

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.

Les classes abstraites en Python pour un code plus propre

2 commentaires sur “Les classes abstraites en Python pour un code plus propre

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.

Retour en haut