Apprenez en lisant dans l'ordre

Le constructeur __init__ et le destructeur __del__

Un guide pratique de __init__ et __del__ en Python. Attributs requis, valeurs par défaut, et la méthode de nettoyage appelée à del ou à la fin du programme.

La dernière fois tu as vu les bases des classes et instances, self, et la forme la plus simple de __init__. Cette fois on va plus loin, en utilisant __init__ pour s'assurer que chaque instance naît dans un état valide. On couvrira les arguments requis qui échouent bruyamment quand tu les oublies, les arguments par défaut pour les champs optionnels, et le destructeur correspondant __del__.

Ce qui se passe sans __init__

Si ta classe n'initialise pas correctement les attributs, tu peux te retrouver avec des instances dépourvues de ces attributs, et au moment où tu appelles une méthode qui les utilise, tu te prends un AttributeError.

Dans le code ci-dessous, User n'a qu'une méthode set_name — pas d'initialiseur. Appeler display() avant set_name plante parce que self.name n'existe pas encore.

class User:
    def set_name(self, name):
        self.name = name

    def display(self):
        print(self.name)

user = User()
user.display()             # AttributeError: pas d'attribut name
# self.name n'existe pas tant que tu n'as pas appelé user.set_name("Alice")

Confirme qu'appeler une méthode avant d'initialiser ses attributs déclenche une AttributeError. Exécute le code et observe l'erreur.

Éditeur Python

Exécuter le code pour voir le résultat

Utiliser __init__ pour imposer les attributs requis

__init__ est la méthode spéciale que Python appelle automatiquement à la création d'une instance. On l'appelle aussi le constructeur. Les noms entourés de doubles soulignements sont des méthodes dunder — la convention Python pour les méthodes qu'il appelle à des moments spécifiques.

En déclarant __init__(self, name, email) avec des paramètres requis, oublier de passer l'un des deux arrête le programme avec TypeError. Tu ne te retrouves jamais avec un User() incomplet, donc tout le code qui suit peut compter sur le fait que « name et email sont définitivement définis ».

Comment __init__ s'accroche à la création d'instance
User('Alice', 'alice@x.com')construit instancevideappel__init__userattributs définis
Appeler User("Alice", "alice@x.com") fait que Python construit une instance vide, exécute __init__, remplit les attributs et renvoie l'instance terminée. Oublie un argument et __init__ lui-même lève TypeError.
class User:
    def __init__(self, name, email):    # paramètres requis
        print("__init__ appelé")
        self.name = name
        self.email = email

    def display(self):
        print(f"{self.name} <{self.email}>")

user = User("Alice", "alice@example.com")  # définit user.name à "Alice" et user.email à "alice@example.com"
# __init__ appelé
user.display()
# Alice <alice@example.com>

# Oublier un argument lève TypeError
# User()  # → TypeError: missing 2 required positional arguments

Écris __init__ avec deux paramètres requis et essaie la création, l'affichage et ce qui se passe quand tu en oublies un.

① Définis class User:. À l'intérieur de __init__(self, name, email), écris self.name = name et self.email = email.

② Définis def display(self): qui affiche f"{self.name} <{self.email}>".

③ Construis alice = User("Alice", "alice@example.com") et appelle alice.display(). Confirme que ça affiche Alice <alice@example.com>.

④ Puis essaie lea = User("Léa")en oubliant l'email — et confirme que ça s'arrête avec TypeError (entoure-le avec try / except pour inspecter le message).

(Si tu l'exécutes correctement, une explication apparaîtra.)

Éditeur Python

Exécuter le code pour voir le résultat

Arguments par défaut pour les champs optionnels

__init__ est une fonction normale, donc tu peux lui donner des arguments par défaut. Écris quelque chose comme age=0, et l'appelant peut soit le sauter (et obtenir la valeur par défaut), soit passer une valeur. Cela te permet de scinder ta conception entre « attributs requis » et « attributs optionnels ».

class User:
    def __init__(self, name, email, age=0):   # age est optionnel
        self.name = name
        self.email = email
        self.age = age

alice = User("Alice", "alice@example.com")            # age omis → 0
lea = User("Léa", "lea@example.com", 30)              # age fourni

print(alice.age)   # 0
print(lea.age)     # 30

Ne mets pas list / dict directement dans un argument par défaut

Écrire un objet mutable comme valeur par défaut — comme def __init__(self, tags=[]) — tombe dans un piège notoire : toutes les instances finissent par partager la même liste. On couvre ça en détail dans Arguments de fonction et mutabilité. C'est le même piège dans __init__, donc le correctif standard est tags=None plus if tags is None: tags = [] à l'intérieur de la fonction.

Écris un argument par défaut sous forme de liste vide directement et observe ce qui ne va pas.

① Définis class User:. À l'intérieur de __init__(self, name, tags=[]), écris self.name = name et self.tags = tags (en mettant intentionnellement tags=[] directement).

② Construis alice = User("Alice") et lea = User("Léa").

③ Exécute alice.tags.append("vip"), puis print(alice.tags) et print(lea.tags) — confirme que lea finit aussi avec "vip".

④ Enfin, print(alice.tags is lea.tags) pour confirmer qu'elles partagent la même liste.

Éditeur Python

Exécuter le code pour voir le résultat

__del__ — Le nettoyage quand une instance disparaît

La méthode dunder qui fait pendant à __init__ est __del__ (le destructeur). Tandis que __init__ s'exécute à la création d'une instance, __del__ s'exécute au moment où une instance est détruite, appelée automatiquement par Python.

Il y a deux principales façons dont la destruction se produit :

- Tu la supprimes explicitement avec del nom_de_variable

- La mémoire est récupérée quand le programme se termine (toutes les instances restantes sont détruites tour à tour)

À cause du second cas, __del__ s'exécute à la fin du programme même si tu ne le déclenches pas toi-même.

Quand __init__ et __del__ s'exécutent
appel User('Alice')__init__se déclencheuseren viedel user/ fin programme__del__se déclenchemémoirelibéréecréerprêtdétruirenettoyer
La rangée du haut est le flux __init__ — il s'exécute à la naissance de l'instance, appelé automatiquement par Python. La rangée du bas est le flux __del__ — il s'exécute à la mort de l'instance, soit par del, soit à la fin du programme.

__del__ ne s'exécute pas dans le navigateur

L'exécution côté navigateur de ce site est basée sur MicroPython, qui par conception n'appelle pas __del__. Les exemples de code de cette section sont à lire pour comprendre le comportement. Exécute-les dans CPython sur ton terminal local si tu veux vraiment voir __del__ se déclencher.

class User:
    def __init__(self, name):
        print(f"créé {name}")
        self.name = name

    def __del__(self):
        print(f"détruit {self.name}")

alice = User("Alice")      # créé Alice
lea = User("Léa")          # créé Léa

del alice                  # détruit Alice  ← se déclenche explicitement ici
print("sur le point de quitter")
# sur le point de quitter
# détruit Léa  ← se déclenche automatiquement à la fin du programme

Un motif de nettoyage courant — Se retirer d'une liste

Un usage typique de __del__ est le nettoyage qui fait pendant à la création — comme « me retirer d'une liste tenue par la classe ». Dans l'exemple ci-dessous, la classe User ajoute le nom à la variable de classe users à la création et le retire à la destruction.

class User:
    users = []                         # variable de classe : utilisateurs actuellement en vie

    def __init__(self, name):
        self.name = name
        User.users.append(name)        # enregistrer à la création

    def __del__(self):
        User.users.remove(self.name)   # retirer à la destruction

alice = User("Alice")
lea = User("Léa")
print(User.users)    # ['Alice', 'Léa']

del alice
print(User.users)    # ['Léa']

L'utilisation directe de __del__ est rare en pratique

Le moment où __del__ s'exécute dépend du ramasse-miettes de Python, donc tu ne peux pas prédire le timing de manière fiable. Pour la libération déterministe de fichiers ou de connexions réseau, utilise plutôt les instructions with (gestionnaires de contexte) que __del__.

Cette fois on a couvert l'utilisation pratique de __init__imposer les attributs requis et les arguments par défaut — et rencontré le __del__ correspondant avec son motif typique. Tu peux maintenant contrôler « la durée de vie d'une instance, de la création à la destruction » depuis Python.

Ensuite, on creusera la vérité derrière self.x = ... que tu écris sans trop y penser. On démêlera les deux types de variables dans une classe — variables de classe et variables d'instance — et on suivra ce qui se crée réellement quand tu écris self.x = ..., en surveillant les références avec id().

QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Si une classe C a __init__(self, name), que se passe-t-il quand tu appelles C() sans arguments ?

Question 2Quel est le moment correct où __del__ s'exécute ?

Question 3Pourquoi écrire def __init__(self, tags=[]) est-il considéré comme problématique ?