Qu'est-ce que __init__ en Python ?

Découvrez la méthode __init__ en Python : le constructeur de classe qui initialise les objets. Syntaxe, exemples pratiques et bonnes pratiques pour bien l'utiliser.
10 min de lecture
Believemy logo

En Python, la méthode __init__ est l'une des méthodes spéciales les plus fondamentales du langage. Appelée automatiquement lors de la création d'un objet, elle permet d'initialiser les attributs d'une instance de class.

Si vous souhaitez maîtriser la programmation orientée objet en Python, comprendre __init__ est absolument indispensable.

Que vous soyez débutant ou développeur confirmé, notre formation complète sur Python vous accompagne pas à pas dans l'apprentissage de ces concepts essentiels.

 

Définition de __init__ en Python

La méthode __init__ est une méthode spéciale (aussi appelée dunder method, pour "double underscore") qui sert de constructeur en Python. Son rôle est d'initialiser un nouvel objet juste après sa création en mémoire.

Concrètement, lorsque vous créez une instance d'une classe avec MaClasse(), Python effectue deux opérations en coulisses :

  1. __new__ : crée l'objet en mémoire (rarement surchargé).
  2. __init__ : initialise l'objet avec les valeurs que vous lui passez.
Bon à savoir

Techniquement, __init__ n'est pas un "constructeur" au sens strict (c'est __new__ qui crée l'objet), mais on l'appelle ainsi par convention car c'est lui qui configure l'objet pour son utilisation.

La syntaxe de base est la suivante :

PYTHON
class MaClasse:
    def __init__(self):
        # Initialisation des attributs
        self.attribut = valeur

Le premier paramètre de __init__ est toujours self, qui représente l'instance en cours de création. Vous pouvez ensuite ajouter autant de paramètres supplémentaires que nécessaire.

 

Syntaxe et paramètres de __init__

La méthode __init__ se définit à l'intérieur d'une class grâce au mot-clé def. Voici sa syntaxe complète avec différents types de paramètres :

__init__ avec des paramètres simples

PYTHON
class Utilisateur:
    def __init__(self, nom, email, age):
        self.nom = nom
        self.email = email
        self.age = age

# Création d'une instance
user = Utilisateur("Alice", "alice@example.com", 30)
print(user.nom)    # Alice
print(user.email)  # alice@example.com
print(user.age)    # 30

Dans cet exemple, nom, email et age sont des paramètres obligatoires. Si vous essayez de créer un Utilisateur sans les fournir, Python lèvera une erreur TypeError.

 

__init__ avec des valeurs par défaut

Vous pouvez définir des valeurs par défaut pour certains paramètres, les rendant ainsi facultatifs :

PYTHON
class Utilisateur:
    def __init__(self, nom, email, age=18, role="membre"):
        self.nom = nom
        self.email = email
        self.age = age
        self.role = role

# Avec les valeurs par défaut
user1 = Utilisateur("Bob", "bob@example.com")
print(user1.age)   # 18
print(user1.role)  # membre

# En surchargeant les valeurs par défaut
user2 = Utilisateur("Charlie", "charlie@example.com", age=25, role="admin")
print(user2.age)   # 25
print(user2.role)  # admin

 

__init__ avec *args et **kwargs

Pour une flexibilité maximale, vous pouvez utiliser *args et **kwargs :

PYTHON
class ConfigServeur:
    def __init__(self, host, port, **options):
        self.host = host
        self.port = port
        self.options = options

serveur = ConfigServeur("localhost", 8080, debug=True, timeout=30)
print(serveur.options)  # {'debug': True, 'timeout': 30}

 

Exemples pratiques et cas d'utilisation

Voyons maintenant des exemples concrets et progressifs pour bien comprendre comment __init__ s'utilise dans des situations réelles.

Initialisation avec validation des données

Un usage très courant de __init__ est de valider les données dès la création de l'objet :

PYTHON
class CompteBancaire:
    def __init__(self, titulaire, solde_initial=0):
        if not isinstance(titulaire, str) or len(titulaire) == 0:
            raise ValueError("Le titulaire doit être une chaîne non vide.")
        if solde_initial < 0:
            raise ValueError("Le solde initial ne peut pas être négatif.")
        
        self.titulaire = titulaire
        self.solde = solde_initial
        self.historique = []
    
    def deposer(self, montant):
        self.solde += montant
        self.historique.append(f"Dépôt : +{montant}€")
    
    def retirer(self, montant):
        if montant > self.solde:
            raise ValueError("Solde insuffisant.")
        self.solde -= montant
        self.historique.append(f"Retrait : -{montant}€")

# Utilisation
compte = CompteBancaire("Alice", 1000)
compte.deposer(500)
compte.retirer(200)
print(compte.solde)       # 1300
print(compte.historique)  # ['Dépôt : +500€', 'Retrait : -200€']
Attention

Attention : ne modifiez jamais directement des attributs qui devraient être protégés. Utilisez des propriétés (@property) pour contrôler l'accès si nécessaire.

 

Initialisation avec des attributs calculés

La méthode __init__ peut aussi calculer des attributs à partir des paramètres reçus :

PYTHON
class Rectangle:
    def __init__(self, largeur, hauteur):
        self.largeur = largeur
        self.hauteur = hauteur
        self.aire = largeur * hauteur
        self.perimetre = 2 * (largeur + hauteur)
    
    def __repr__(self):
        return f"Rectangle({self.largeur}, {self.hauteur})"

rect = Rectangle(10, 5)
print(rect.aire)       # 50
print(rect.perimetre)  # 30
print(rect)            # Rectangle(10, 5)

 

Héritage et appel au __init__ parent avec super()

Lorsque vous créez une classe qui hérite d'une autre, il est essentiel d'appeler le __init__ de la classe parent pour que l'objet soit correctement initialisé :

PYTHON
class Animal:
    def __init__(self, nom, espece):
        self.nom = nom
        self.espece = espece
        self.vivant = True

class Chien(Animal):
    def __init__(self, nom, race, age):
        super().__init__(nom, espece="Chien")
        self.race = race
        self.age = age
    
    def aboyer(self):
        return f"{self.nom} fait Wouf !"

class Chat(Animal):
    def __init__(self, nom, couleur):
        super().__init__(nom, espece="Chat")
        self.couleur = couleur
    
    def miauler(self):
        return f"{self.nom} fait Miaou !"

rex = Chien("Rex", "Berger Allemand", 5)
print(rex.nom)     # Rex
print(rex.espece)  # Chien
print(rex.race)    # Berger Allemand
print(rex.aboyer()) # Rex fait Wouf !

felix = Chat("Félix", "noir")
print(felix.miauler())  # Félix fait Miaou !
Bon à savoir

La fonction super() permet d'appeler les méthodes de la classe parente. C'est la manière recommandée de gérer l'héritage en Python, plutôt que d'appeler directement Animal.__init__(self, ...).

 

Comparaison avec les dataclasses

Python propose les dataclass qui génèrent automatiquement la méthode __init__ pour vous. Voyons la différence :

PYTHON
from dataclasses import dataclass

# Sans dataclass : __init__ manuel
class ProduitClassique:
    def __init__(self, nom, prix, stock=0):
        self.nom = nom
        self.prix = prix
        self.stock = stock

# Avec dataclass : __init__ généré automatiquement
@dataclass
class ProduitDataclass:
    nom: str
    prix: float
    stock: int = 0

# Les deux fonctionnent de la même manière
p1 = ProduitClassique("Clavier", 49.99, 10)
p2 = ProduitDataclass("Clavier", 49.99, 10)

print(p1.nom, p1.prix)  # Clavier 49.99
print(p2.nom, p2.prix)  # Clavier 49.99
Critère__init__ manuel@dataclass
FlexibilitéTotale (logique personnalisée)Limitée (via __post_init__)
VerbositéPlus de code à écrireConcis et lisible
Méthodes auto-généréesAucune__repr__, __eq__, etc.
Cas d'usage idéalLogique complexe d'initialisationObjets de données simples

 

Bonnes pratiques pour __init__

Voici les règles essentielles pour écrire de bonnes méthodes __init__ en Python :

1. Gardez __init__ simple et focalisé

Le rôle de __init__ est d'initialiser l'objet, pas d'effectuer des opérations complexes. Si votre constructeur fait plus de 15-20 lignes, c'est probablement le signe qu'il faut refactoriser :

PYTHON
# ❌ Mauvaise pratique : trop de logique dans __init__
class RapportVentes:
    def __init__(self, fichier):
        self.fichier = fichier
        self.donnees = self._charger_fichier(fichier)  # opération lourde
        self.total = sum(d['montant'] for d in self.donnees)
        self.moyenne = self.total / len(self.donnees)
        self._envoyer_notification()  # effet de bord

# ✅ Bonne pratique : __init__ simple, méthodes séparées
class RapportVentes:
    def __init__(self, fichier):
        self.fichier = fichier
        self.donnees = []
        self.total = 0
        self.moyenne = 0
    
    def charger(self):
        self.donnees = self._charger_fichier(self.fichier)
        self.total = sum(d['montant'] for d in self.donnees)
        self.moyenne = self.total / len(self.donnees)
        return self

 

2. Initialisez tous les attributs dans __init__

Tous les attributs d'instance doivent être définis dans __init__, même s'ils sont initialisés à None. Cela rend votre code plus lisible et facilite la docstring :

PYTHON
class Connexion:
    def __init__(self, url, timeout=30):
        self.url = url
        self.timeout = timeout
        self.session = None       # Sera initialisé plus tard
        self.est_connecte = False  # État initial explicite
        self.derniere_erreur = None

 

3. Évitez les objets mutables comme valeurs par défaut

C'est l'un des pièges les plus classiques en Python :

PYTHON
# ❌ DANGER : la liste est partagée entre toutes les instances !
class Equipe:
    def __init__(self, nom, membres=[]):
        self.nom = nom
        self.membres = membres

e1 = Equipe("Alpha")
e1.membres.append("Alice")

e2 = Equipe("Beta")
print(e2.membres)  # ['Alice'] — Surprise !

# ✅ Bonne pratique : utilisez None et créez une nouvelle liste
class Equipe:
    def __init__(self, nom, membres=None):
        self.nom = nom
        self.membres = membres if membres is not None else []
Attention

Les objets mutables (list, dict, sets) utilisés comme valeurs par défaut sont partagés entre toutes les instances. Utilisez toujours None comme valeur par défaut, puis créez un nouvel objet dans le corps de __init__.

 

4. Utilisez les annotations de type

Les annotations de type améliorent la lisibilité et permettent aux outils d'analyse statique de vérifier votre code :

PYTHON
from typing import Optional

class Article:
    def __init__(self, titre: str, contenu: str, auteur: str, tags: Optional[list[str]] = None) -> None:
        self.titre = titre
        self.contenu = contenu
        self.auteur = auteur
        self.tags = tags if tags is not None else []

 

5. Utilisez des méthodes de classe alternatives

Si vous avez besoin de plusieurs façons de créer un objet, utilisez des méthodes de classe avec @classmethod plutôt que de surcharger __init__ :

PYTHON
import json

class Configuration:
    def __init__(self, host: str, port: int, debug: bool = False):
        self.host = host
        self.port = port
        self.debug = debug
    
    @classmethod
    def depuis_json(cls, chemin_fichier: str):
        with open(chemin_fichier) as f:
            data = json.load(f)
        return cls(data['host'], data['port'], data.get('debug', False))
    
    @classmethod
    def defaut(cls):
        return cls('localhost', 8080, debug=True)

# Différentes façons de créer l'objet
config1 = Configuration("192.168.1.1", 3000)
config2 = Configuration.depuis_json("config.json")
config3 = Configuration.defaut()

 

__init__ et les méthodes spéciales associées

La méthode __init__ fait partie d'un écosystème plus large de méthodes spéciales en Python. Voici les plus importantes à connaître en relation avec l'initialisation :

MéthodeRôleQuand est-elle appelée ?
__new__Crée l'instance en mémoireAvant __init__
__init__Initialise l'instanceJuste après __new__
__post_init__Post-initialisation (dataclasses)Après __init__ auto-généré
__del__Nettoyage avant destructionQuand l'objet est détruit
__repr__Représentation développeurrepr(objet)
__str__Représentation utilisateurstr(objet) / print

Voici un exemple combinant __init__ avec d'autres méthodes spéciales :

PYTHON
class Vecteur:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y
    
    def __repr__(self):
        return f"Vecteur({self.x}, {self.y})"
    
    def __str__(self):
        return f"({self.x}, {self.y})"
    
    def __add__(self, autre):
        return Vecteur(self.x + autre.x, self.y + autre.y)
    
    def __eq__(self, autre):
        return self.x == autre.x and self.y == autre.y
    
    def __len__(self):
        return int((self.x ** 2 + self.y ** 2) ** 0.5)

v1 = Vecteur(3, 4)
v2 = Vecteur(1, 2)
v3 = v1 + v2

print(v3)          # (4, 6)
print(repr(v3))    # Vecteur(4, 6)
print(v1 == v2)    # False

 

Questions fréquentes

Question

Quelle est la différence entre __init__ et __new__ en Python ?

__new__ est responsable de la création de l'instance en mémoire. Il reçoit la classe (cls) en premier paramètre et retourne un nouvel objet. __init__ est ensuite appelé pour initialiser cet objet avec des attributs. Dans 99 % des cas, vous n'avez besoin que de __init__. __new__ n'est utile que dans des cas avancés comme les singletons ou les classes immuables (par exemple, pour sous-classer des tuple ou des str).

 

Question

Pourquoi faut-il toujours mettre self comme premier paramètre de __init__ ?

self est une convention Python qui désigne l'instance en cours de création. Lorsque vous écrivez objet = MaClasse(args), Python appelle en interne MaClasse.__init__(objet, args). Le paramètre self reçoit donc automatiquement la référence à l'objet. Bien que vous puissiez techniquement utiliser un autre nom, self est une convention universelle en Python et ne pas la respecter rendrait votre code très difficile à lire.

 

Question

__init__ peut-il retourner une valeur ?

Non. La méthode __init__ ne doit jamais retourner autre chose que None. Si vous essayez de retourner une valeur, Python lèvera une erreur TypeError: __init__() should return None. Son rôle est uniquement d'initialiser l'objet, pas de créer ou retourner quoi que ce soit. Si vous avez besoin de contrôler ce qui est retourné lors de la création, c'est __new__ que vous devez surcharger.

 

Question

Comment apprendre à maîtriser __init__ et la POO en Python ?

Pour bien comprendre __init__ et l'ensemble de la programmation orientée objet en Python, rien ne vaut une approche structurée et progressive. Notre formation dédiée à Python sur Believemy vous guide de la découverte des bases jusqu'aux concepts avancés comme l'héritage, les design patterns et les méthodes spéciales. Vous y trouverez des exercices pratiques et des projets concrets pour consolider vos compétences.

Termes connexes

Découvrez notre glossaire Python

Parcourez les termes et définitions les plus couramment utilisés dans le domaine du développement avec Python.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.