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 :
__new__: crée l'objet en mémoire (rarement surchargé).__init__: initialise l'objet avec les valeurs que vous lui passez.
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 :
class MaClasse:
def __init__(self):
# Initialisation des attributs
self.attribut = valeurLe 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
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) # 30Dans 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 :
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 :
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 :
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 : 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 :
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é :
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 !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 :
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 à écrire | Concis et lisible |
| Méthodes auto-générées | Aucune | __repr__, __eq__, etc. |
| Cas d'usage idéal | Logique complexe d'initialisation | Objets 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 :
# ❌ 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 :
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 :
# ❌ 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 []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 :
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__ :
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éthode | Rôle | Quand est-elle appelée ? |
|---|---|---|
__new__ | Crée l'instance en mémoire | Avant __init__ |
__init__ | Initialise l'instance | Juste après __new__ |
__post_init__ | Post-initialisation (dataclasses) | Après __init__ auto-généré |
__del__ | Nettoyage avant destruction | Quand l'objet est détruit |
__repr__ | Représentation développeur | repr(objet) |
__str__ | Représentation utilisateur | str(objet) / print |
Voici un exemple combinant __init__ avec d'autres méthodes spéciales :
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
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).
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.
__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.
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.