Aller au contenu
Développement medium

POO avancée en Python : dataclass, abc, MRO, mixins

11 min de lecture

logo python

La POO avancée en Python regroupe les outils qui vont au-delà des classes de base : les dataclass pour écrire moins de code, les classes abstraites (abc) pour imposer un contrat, l'héritage multiple et le MRO, les mixins, la surcharge d'opérateurs, __slots__ et les métaclasses. Ces mécanismes servent à structurer des applications réelles, réduire la duplication et rendre vos objets prévisibles.

Ce guide s'adresse aux développeurs intermédiaires et avancés qui maîtrisent déjà les bases de la POO (classes, objets, héritage simple, self) et veulent monter d'un cran.

  • Réduire le code répétitif avec les @dataclass
  • Imposer un contrat avec les classes abstraites du module abc
  • Comprendre le MRO et maîtriser l'héritage multiple avec super()
  • Composer des comportements avec les mixins
  • Surcharger les opérateurs (+, ==, <) via les méthodes spéciales
  • Optimiser la mémoire avec __slots__ et découvrir les métaclasses

Écrire une classe qui ne fait que stocker des données impose de répéter __init__, __repr__ et __eq__. Le décorateur @dataclass (module dataclasses, intégré depuis Python 3.7) génère tout cela pour vous à partir des annotations de type.

from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int = 0 # valeur par défaut
p = Point(3, 4)
print(p) # Point(x=3, y=4) -> __repr__ généré
print(p == Point(3, 4)) # True -> __eq__ généré

Une seule déclaration remplace une vingtaine de lignes. Pour un attribut mutable par défaut (liste, dictionnaire), utilisez field(default_factory=...) au lieu d'une valeur directe, afin d'éviter le partage entre instances.

from dataclasses import dataclass, field
@dataclass
class Panier:
articles: list = field(default_factory=list)
a, b = Panier(), Panier()
a.articles.append("pain")
print(a.articles, b.articles) # ['pain'] [] -> pas de partage

Une classe abstraite définit des méthodes que les classes filles doivent implémenter, sans fournir elle-même de corps utile. C'est le moyen d'exprimer une interface en Python, grâce au module abc (Abstract Base Classes).

from abc import ABC, abstractmethod
class Forme(ABC):
@abstractmethod
def aire(self) -> float:
...
class Cercle(Forme):
def __init__(self, rayon):
self.rayon = rayon
def aire(self) -> float:
return 3.14159 * self.rayon ** 2
print(Cercle(2).aire()) # 12.56636

Tant qu'une classe fille n'implémente pas toutes les méthodes abstraites, Python refuse de l'instancier :

# Forme() # TypeError: Can't instantiate abstract class Forme
# # with abstract method aire

Les classes abstraites documentent le contrat attendu et font échouer tôt (à l'instanciation) plutôt que tard (à l'appel d'une méthode manquante).

Python autorise l'héritage multiple : une classe peut hériter de plusieurs parents. Pour savoir quelle méthode est appelée, Python suit le MRO (Method Resolution Order), l'ordre de résolution des méthodes, calculé par l'algorithme C3.

class A:
def salut(self):
return "A"
class B(A):
def salut(self):
return "B -> " + super().salut()
class C(A):
def salut(self):
return "C -> " + super().salut()
class D(B, C):
def salut(self):
return "D -> " + super().salut()
print(D().salut()) # D -> B -> C -> A
print([cls.__name__ for cls in D.__mro__]) # ['D', 'B', 'C', 'A', 'object']

Le point clé : super() ne signifie pas « la classe parente » mais « la suivante dans le MRO ». C'est ce qui permet à D de traverser B puis C puis A sans appeler A deux fois. Consultez toujours Classe.__mro__ en cas de doute sur l'ordre.

Un mixin est une petite classe qui ajoute un comportement précis, destinée à être combinée avec d'autres par héritage multiple. Elle ne s'utilise jamais seule et ne définit pas d'état complet.

from dataclasses import dataclass
import json
class JSONMixin:
def to_json(self) -> str:
return json.dumps(self.__dict__)
@dataclass
class Utilisateur(JSONMixin):
nom: str
age: int
print(Utilisateur("Alice", 30).to_json()) # {"nom": "Alice", "age": 30}

Les mixins modularisent les fonctionnalités : sérialisation, comparaison, journalisation. Vous composez une classe en assemblant les briques dont elle a besoin, sans dupliquer le code.

La surcharge d'opérateurs définit le comportement de +, ==, <, etc. pour vos objets, via des méthodes spéciales. Vos classes s'intègrent alors naturellement au langage.

class Vecteur:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, autre):
return Vecteur(self.x + autre.x, self.y + autre.y)
def __eq__(self, autre):
return (self.x, self.y) == (autre.x, autre.y)
def __repr__(self):
return f"Vecteur({self.x}, {self.y})"
print(Vecteur(1, 2) + Vecteur(3, 4)) # Vecteur(4, 6)
print(Vecteur(1, 2) == Vecteur(1, 2)) # True

Chaque opérateur correspond à une méthode : __add__ pour +, __sub__ pour -, __lt__ pour <, __mul__ pour *. Utilisez cette possibilité avec parcimonie, seulement quand l'opération a un sens intuitif pour l'objet.

Le décorateur @property transforme une méthode en attribut. Au-delà du simple getter, il permet des attributs calculés et une validation à l'écriture, sans changer l'interface publique.

class Temperature:
def __init__(self, celsius):
self.celsius = celsius # passe par le setter
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, valeur):
if valeur < -273.15:
raise ValueError("En dessous du zéro absolu")
self._celsius = valeur
@property
def fahrenheit(self): # propriété calculée, en lecture seule
return self._celsius * 9 / 5 + 32
t = Temperature(25)
print(t.fahrenheit) # 77.0
t.celsius = 30 # validé par le setter
print(t.fahrenheit) # 86.0

L'utilisateur écrit t.celsius = 30 comme un attribut normal, mais la validation s'applique. fahrenheit est recalculé à chaque lecture : aucune donnée redondante à maintenir.

Par défaut, chaque instance stocke ses attributs dans un dictionnaire __dict__, souple mais coûteux en mémoire. __slots__ fige la liste des attributs autorisés et supprime ce dictionnaire, ce qui réduit l'empreinte mémoire et accélère l'accès, utile quand vous créez des millions d'objets.

class PointOptimise:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x, self.y = x, y
p = PointOptimise(1, 2)
print(p.x, p.y) # 1 2
# p.z = 3 # AttributeError: 'PointOptimise' object has no attribute 'z'

En contrepartie, vous ne pouvez plus ajouter d'attribut hors de la liste __slots__. C'est une optimisation ciblée, pas un réflexe systématique.

Une métaclasse est la classe d'une classe : elle contrôle comment les classes elles-mêmes sont créées. C'est un outil puissant mais rarement nécessaire, réservé aux frameworks (ORM, validation de schémas). L'exemple classique est le singleton, qui garantit une instance unique.

class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Config(metaclass=Singleton):
pass
print(Config() is Config()) # True : toujours la même instance
  • @dataclass génère __init__, __repr__ et __eq__ ; field(default_factory=...) pour les défauts mutables, frozen=True pour l'immuabilité.
  • Les classes abstraites (abc.ABC + @abstractmethod) imposent un contrat et empêchent l'instanciation tant qu'il n'est pas rempli.
  • super() suit le MRO, pas la classe parente : inspectez Classe.__mro__ en héritage multiple.
  • Un mixin ajoute un comportement ciblé, à combiner par héritage, jamais utilisé seul.
  • La surcharge d'opérateurs (__add__, __eq__, __lt__) intègre vos objets au langage, à réserver aux cas intuitifs.
  • @property offre attributs calculés et validation ; __slots__ réduit la mémoire au prix de la souplesse.
  • Les métaclasses contrôlent la création des classes : puissantes, mais à éviter sauf besoin réel.

Les réponses courtes ci-dessous couvrent les questions les plus recherchées sur la POO avancée en Python. Chaque exemple est autonome et testé sur Python 3.12.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn