2.3Programmation orientée objet (classes, héritage, dunder)
L'essentiel en 30 secondes
- La classe est le moule, l'objet la pièce : attributs (données) + méthodes (verbes sur self) voyagent ensemble — fini les dicts + fonctions dispersées.
- Héritage = est-un : l'enfant spécialise le parent via super(), peu profond (≤ 3) — composition pour a-un.
- Dunders = objets citoyens : __str__/__eq__/__len__ répondent à print/==/len — @dataclass pour les porte-données.
Mini-cas filé — Nabil modélise ses ventes en objets
Les dicts de Nabil se dispersent : des fonctions qui manipulent des ventes sans jamais vérifier les montants, des rapports qui dupliquent les mêmes calculs. Il modélise : une classe Vente (wilaya, montant, méthodes total et __str__ lisible), une classe RapportMensuel qui regroupe les ventes, puis une classe VenteRemise qui HÉRITE de Vente et ajoute un taux (super().__init__() réutilise le parent). Fini les dicts + fonctions éparpillées : données et comportements vivent ensemble. En 2.4, la création refusera les montants négatifs avec raise.
Suite du fil rouge 2.2 : les lignes de Nabil deviennent des objets — et continue en 2.4.
1. La classe : le moule, l'objet : la pièce
class Vente: + __init__(self, ...) définit le moule ; v = Vente("Alger", 1200) crée la pièce. self désigne l'objet lui-même dans ses méthodes : chaque objet accède à SES attributs (self.montant) indépendamment des autres. Les attributs sont les données, les méthodes sont les comportements (verbes sur self, jamais de global). Données + comportements voyagent ensemble : fini les dictionnaires + fonctions dispersées. Nabil crée ainsi deux ventes distinctes qui ne se mélangent jamais.
2. Héritage : spécialiser, pas copier
class VenteRemise(Vente): hérite et spécialise (taux, calcul remisé). super().__init__() réutilise le constructeur du parent au lieu de le recopier ; redéfinir une méthode dans l'enfant, c'est le polymorphisme. Règle : héritage pour du « est-un » (une VenteRemise est-une Vente), composition (« a-un » : un Rapport a-des Ventes) sinon. Trois niveaux max : au-delà, c'est un labyrinthe. Nabil ajoute ainsi les ventes remisées sans toucher au code existant.
3. Dunder : parler python couramment
Les méthodes dunder (double underscore) intègrent vos objets aux mécanismes natifs : __str__ pour un affichage lisible (print(v)), __eq__ pour l'égalité (==), __len__ pour la taille (len()). Sans elles, print(objet) affiche un cryptique <... object at 0x...>. Pour les porte-données simples, @dataclass génère __init__, __eq__ et __repr__ automatiquement. Les objets de Nabil répondent ainsi à print() et == comme les types natifs.
🔎 Exemple concret — 🔎 Exemple concret — Les ventes de Nabil qui s'affichent
Objets lisibles et comparables, comme chez Nabil :
class Vente:
def __init__(self, wilaya, montant):
self.wilaya = wilaya
self.montant = float(montant)
def __str__(self):
return f"{self.wilaya} : {self.montant:.2f} €"
class VenteRemise(Vente):
def __init__(self, wilaya, montant, taux=0.1):
super().__init__(wilaya, montant)
self.taux = taux
def net(self):
return self.montant * (1 - self.taux)
v = VenteRemise("Alger", 1000)
print(v) # Alger : 1000.00 €
print(f"Net : {v.net():.2f} €") # Net : 900.00 €
Lecture : __init__ initialise, __str__ affiche, super() réutilise le parent.
Leçon : méthodes = verbes sur self, jamais de global.
Vocabulaire de la section
- Classe / objet
- La classe est le plan (moule) ; l'objet (instance) est une réalisation concrète créée à partir de ce plan : v = Vente(...).
Exemple simple : Nabil crée deux ventes distinctes depuis la même classe.
Ce que ce n'est pas : un dictionnaire libre sans règles ni comportements.
Réflexe : concept à modéliser = classe.
- Attribut / méthode
- L'attribut est une donnée de l'objet (self.montant) ; la méthode est une fonction attachée à l'objet, verbe sur self.
Exemple simple : La vente de Nabil porte wilaya et calcule net().
Ce que ce n'est pas : des fonctions globales dispersées loin des données.
Réflexe : verbe sur les données = méthode.
- __init__ / self
- __init__ est le constructeur, appelé à la création ; self désigne l'objet courant, écrit dans la définition mais passé automatiquement à l'appel.
Exemple simple : Vente("Alger", 1200) initialise self.wilaya et self.montant.
Ce que ce n'est pas : un paramètre à passer à la main à chaque appel.
Réflexe : créer un objet = __init__ via self.
- Héritage
- Une classe enfant hérite des attributs et méthodes d'un parent et les spécialise ; super() réutilise le parent. Réservé au « est-un », peu profond.
Exemple simple : VenteRemise hérite de Vente et ajoute le taux.
Ce que ce n'est pas : un copier-coller de la classe parent.
Réflexe : est-un = héritage ; a-un = composition.
- Méthode dunder
- Méthode à double underscore (__str__, __len__, __eq__) qui branche vos objets sur print(), ==, len() ; @dataclass les génère pour les porte-données.
Exemple simple : print(vente) affiche « Alger : 1000.00 € » grâce à __str__.
Ce que ce n'est pas : un affichage cryptique
Réflexe : objet affichable/comparable = dunder.
En pratique — En pratique — Créer des classes et de l'héritage
- Définissez une classe Vente avec __init__(self, wilaya, montant) et une méthode afficher(). Créez deux objets distincts et vérifiez leur indépendance.
- Ajoutez __str__ pour un affichage lisible (print(v) → « Alger : 1000.00 € ») et __eq__ pour comparer deux ventes.
- Créez une classe VenteRemise qui HÉRITE de Vente, ajoute un taux et une méthode net(). Réutilisez le parent avec super().__init__().
- Redéfinissez net() dans l'enfant (polymorphisme) et testez @dataclass sur une classe porte-données simple.
Script prêt à dire — défendre la classe devant l'équipe (30 s)
« Pour les ventes de Nabil, j'ai remplacé les dicts dispersés par une classe Vente : wilaya et montant initialisés en __init__, calculs en méthodes sur self — chaque objet est indépendant. Les remises héritent avec super(), et __str__ donne un affichage propre. On ne touche plus aux données sans passer par l'objet. »
À dire en montrant un dict bricolé puis le même cas en classe.
Modèle copier-coller — classe, héritage et dunder
class Vente:
def __init__(self, wilaya, montant):
if float(montant) < 0:
raise ValueError("montant négatif interdit")
self.wilaya = wilaya
self.montant = float(montant)
def __str__(self):
return f"{self.wilaya} : {self.montant:.2f} €"
def __eq__(self, autre):
if not isinstance(autre, Vente):
return NotImplemented
return (self.wilaya, self.montant) == (autre.wilaya, autre.montant)
class VenteRemise(Vente):
def __init__(self, wilaya, montant, taux=0.1):
super().__init__(wilaya, montant)
self.taux = taux
def net(self):
return self.montant * (1 - self.taux)
Vente simple
Une Vente de Nabil porte wilaya + montant et s'affiche en f-string via __str__.
Vente remisée
VenteRemise hérite et ajoute le taux : super() réutilise le parent, net() calcule.
Rapport mensuel
Le Rapport a-des Ventes (composition) : il totalise sans hériter — a-un, pas est-un.
Porte-données
Un récap simple devient @dataclass : __init__ et __eq__ générés, zéro code répétitif.
✓ Bonnes pratiques
- Regrouper données + comportements en classe (jamais de dict + fonctions dispersées).
- Hériter peu profond (≤ 3) pour est-un ; composer pour a-un ; super() pour réutiliser.
- Ajouter __str__/__eq__ et @dataclass pour des objets citoyens.
✗ Pièges fréquents
- Tout garder en dict + fonctions globales dispersées.
- Utiliser l'héritage pour du « a-un » au lieu de la composition.
- Empiler 6 niveaux d'héritage illisibles.
Points clés à retenir
- La classe est le moule ; l'objet (instance) en est une réalisation.
- __init__ initialise l'objet ; self désigne l'objet courant (passé automatiquement).
- L'héritage permet à une classe enfant de réutiliser/étendre une classe parent (est-un, super()).
- Les méthodes dunder (__str__…) intègrent vos objets aux mécanismes de Python ; @dataclass pour les porte-données.
Défi 48h — appliquez demain
Modélisez un vrai concept (celui de Nabil ou le vôtre) en classe avec __init__ + __str__, créez deux objets indépendants, ajoutez une classe enfant avec super() et testez l'égalité avec __eq__.
- Je peux expliquer classe vs objet et le rôle de self.
- Je sais créer une classe, une méthode et un héritage avec super().
- J'ai rendu mes objets lisibles avec __str__ (et @dataclass si adapté).
Questions fréquentes
À quoi sert self exactement ?
self représente l'objet sur lequel on appelle la méthode. Il permet à chaque objet d'accéder à SES propres attributs (self.montant) indépendamment des autres. Python le passe automatiquement à l'appel : vous l'écrivez dans la définition, pas à l'appel.
La POO est-elle indispensable pour débuter ?
Pour de petits scripts, non : les fonctions suffisent. Mais dès que les programmes grandissent — et pour comprendre les bibliothèques (pandas, Django…) qui l'utilisent partout — la POO devient essentielle. Investir ici paie sur le long terme.
Que désigne « self » dans une méthode ?