Kodokon kodokon.com

POO : classes, __init__, méthodes et héritage

Structurez votre code avec des classes bien conçues : initialisation, attributs d'instance ou de classe, héritage et ses compromis.

10 min · 3 questions

Ouvrir cette leçon dans Kodokon

Vous manipulez des objets Python en permanence : chaînes, listes, fichiers. Écrire vos propres classes consiste à regrouper un état (les attributs) et un comportement (les méthodes) derrière une interface cohérente. Point de vocabulaire souvent mal compris : __init__ n'est pas un constructeur. L'objet est déjà créé (par __new__) quand __init__ reçoit self ; son rôle est d'initialiser l'instance. Et self n'a rien de magique : c'est le premier paramètre de toute méthode d'instance, que Python passe automatiquement à l'appel.

PYTHON
class Account:
    def __init__(self, owner, balance=0.0):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("amount must be > 0")
        self.balance += amount
        return self.balance

    def __repr__(self):
        return f"Account({self.owner!r}, {self.balance})"

account = Account("Ada")
account.deposit(120.0)
print(account)
Une classe complète : __init__, méthode métier et __repr__

Distinction cruciale en production : les attributs de classe sont déclarés dans le corps de la classe et partagés par toutes les instances ; les attributs d'instance sont créés sur self, généralement dans __init__. À la lecture, Python cherche d'abord sur l'instance, puis remonte à la classe, puis aux classes parentes. Une affectation self.x = ... crée toujours un attribut d'instance, qui masque l'attribut de classe du même nom. Le piège classique du métier : un attribut de classe mutable.

PYTHON
class Basket:
    items = []

    def add(self, item):
        self.items.append(item)

a = Basket()
b = Basket()
a.add("apple")
print(b.items)  # ['apple'] : liste partagée !
Le piège de l'attribut de classe mutable

L'héritage exprime une relation « est un » : Car est un Vehicle. La classe fille hérite des méthodes, peut les redéfinir, et super() lui permet d'appeler la version parente - indispensable dans __init__ pour ne pas court-circuiter l'initialisation du parent. Python résout les appels en suivant le MRO (Method Resolution Order). Ne systématisez pas l'héritage : pour réutiliser du code sans relation « est un », préférez la composition (un objet en possède un autre). Les hiérarchies profondes sont fragiles : chaque classe parente devient une API implicite pour toutes ses filles.

PYTHON
class Vehicle:
    def __init__(self, brand):
        self.brand = brand

    def describe(self):
        return self.brand

class Car(Vehicle):
    def __init__(self, brand, doors):
        super().__init__(brand)
        self.doors = doors

    def describe(self):
        base = super().describe()
        return f"{base}, {self.doors} portes"

print(Car("Aster", 5).describe())
Héritage et super() : étendre sans réécrire

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Quel est le rôle exact de __init__ ?
    • Créer l'objet en mémoire
    • Initialiser une instance déjà créée
    • Déclarer les attributs de classe
    • Être appelé à chaque accès à un attribut
  2. Une classe déclare items = [] dans son corps. Après a.add(1) qui fait self.items.append(1), que voit une autre instance b ?
    • b.items est une liste vide
    • b.items contient 1 : la liste est partagée
    • Python lève une AttributeError
  3. Dans Car.__init__, à quoi sert l'appel super().__init__(brand) ?
    • À créer une seconde instance de Vehicle
    • À exécuter l'initialisation définie dans la classe parente
    • À copier les méthodes de Vehicle dans Car
    • À vérifier que Car hérite bien de Vehicle