Le guide complet sur les classes en Python
En Python, le mot-clé class
permet de définir ce que l’on appelle une classe, c’est-à-dire une structure qui modélise un concept ou un objet du monde réel.
Les classes sont un pilier fondamental de la programmation orientée objet (ce qu'on abrège POO), un paradigme largement utilisé dans les projets Python de moyenne et grande envergure.
Créer une classe revient à définir un modèle générique qui pourra ensuite être instancié plusieurs fois. Ces instances sont appelées objets.
Une classe, c'est comme un plan d’architecte. L’objet c'est la maison qu’on construit à partir de ce plan.
Définition d'une classe
Une classe est une abstraction. Elle regroupe des données (appelées attributs) et des comportements (appelés méthodes) dans une même structure logique.
Par exemple, une classe Voiture
pourrait regrouper :
- Des attributs : marque, modèle, couleur ;
- Des méthodes : demarrer(), accelerer(), freiner().
La classe permet donc de modéliser une entité de manière propre et réutilisable, comme nous l'avons vu.
Chaque fois qu’on crée un objet à partir d’une classe, on parle d’instance de cette classe. Ces objets peuvent interagir entre eux, être stockés dans des listes, modifiés, copiés, etc.
Syntaxe de base d'une classe
Voici la syntaxe minimale pour créer une classe en Python :
class Animal:
def __init__(self, nom):
self.nom = nom
def parler(self):
print(f"{self.nom} fait un bruit.")
Décryptons ce que fait cet exemple ! 😋
Dans un premier temps, nous avons le mot-clé class
: il indique ) Python que nous sommes en train d'en créer une.
Ensuite, nous donnons son nom : Animal
.
Une classe doit avoir son nom sous format PascalCase pour respecter les conventions.
Enfin, on utilise la méthode __init__()
qui est appelée automatiquement à la création d'une instance. C'est ce qu'on appelle un constructeur.
À noter aussi que self
fait référence à l'objet courant.
Créer une instance en Python
Une fois qu’une classe est définie, on peut en créer une instance (c’est-à-dire un objet unique basé sur ce modèle) :
# Ce qu'on avait déjà créé
class Animal:
def __init__(self, nom):
self.nom = nom
def parler(self):
print(f"{self.nom} fait un bruit.")
# Ici : on crée notre objet
mon_chien = Animal("Rex")
mon_chien.parler() # Rex fait un bruit.
Ce qu'il se passe dans cet exemple :
Animal("Rex")
appelle automatiquement la méthode__init__()
;self.nom = nom
stocke le nom dans l’objet ;parler()
utilise cet attribut pour afficher une phrase.
Attributs d'instance vs attributs de classe
Il est essentiel de bien distinguer les deux types d’attributs, à savoir les attributs d'instance et les attributs de classe.
Attributs d'instance
Ils sont spécifiques à chaque objet, définis via self
.
class Voiture:
def __init__(self, marque):
self.marque = marque
Chaque voiture aura sa propre marque
.
Attributs de classe
Ils sont partagés par toutes les instances (donc tous les objets) de la classe.
class Voiture:
roues = 4 # attribut de classe
print(Voiture.roues) # 4
Les attributs de classe sont utiles pour définir des constantes qui sont communes à toutes les instances.
Tableau comparatif
Attribut d'instance | Attribut de classe |
Défini dans __init__() ou une méthode | Défini directement dans la classe |
Unique à chaque objet | Partagé entre tous les objets |
Accès par self.attribut | Accès par Classe.attribut ou self.attribut |
Les méthodes d'une classe
Une méthode est une fonction définie à l’intérieur d’une classe.
Elle peut :
- Accéder aux données de l’objet (
self.attribut
) ; - Modifier des valeurs internes ;
- Effectuer une action liée à l’état de l’objet.
class Compte:
def __init__(self, titulaire, solde):
self.titulaire = titulaire
self.solde = solde
def deposer(self, montant):
self.solde += montant
print(f"{montant}€ déposés. Nouveau solde : {self.solde}€")
c = Compte("Alice", 100)
c.deposer(50) # 50€ déposés. Nouveau solde : 150€
Les méthodes rendent la classe dynamique et interactive. Elles permettent de faire vivre les objets. 👀
Le principe de l'encapsulation et la visibilité
L’encapsulation est un principe fondamental en programmation orientée objet. Elle consiste à cacher les détails internes d’un objet afin de ne pas exposer des données sensibles ou inutiles à l’extérieur de la classe.
De manière générale, Python ne gère pas la protection des données au sens littéral. Mais il existe des conventions pour voir immédiatement quand une donnée peut être utilisée ou non :
nom
: accessible partout (public) ;_nom
: signalé comme protégé, usage interne recommandé ;__nom
: privé, nom "manglé" pour éviter les collisions.
Allez ! Prenons un petit exemple.
class Banque:
def __init__(self, titulaire):
self._solde = 0 # protégé
self.__titulaire = titulaire # privé
def afficher_solde(self):
return self._solde
Ici, même si _solde
est techniquement accessible, il est conventionnellement réservé à un usage interne. Le double underscore __
empêche l’accès direct depuis l’extérieur (banque.__titulaire
lève une erreur).
Les classes et l'héritage
L’héritage permet de créer une nouvelle classe à partir d’une classe existante. Cela permet de réutiliser et spécialiser des comportements communs.
C’est une manière puissante de réutiliser du code tout en personnalisant certains comportements.
Exemple simple d'héritage
class Animal:
def parler(self):
print("L'animal fait un bruit.")
class Chien(Animal):
pass
rex = Chien()
rex.parler() # Affiche : L'animal fait un bruit.
La classe Chien
n’a pas sa propre méthode parler()
, donc elle hérite directement de celle de Animal
.
Redéfinir une méthode (overriding)
On peut modifier (ou "redéfinir") une méthode héritée pour qu’elle ait un comportement différent dans la classe enfant. C'est ce qu'on appelle l'overriding.
class Animal:
def parler(self):
print("L'animal fait un bruit.")
class Chien(Animal):
def parler(self):
print("Le chien aboie.")
rex = Chien()
rex.parler() # Affiche : Le chien aboie.
Cette version remplace la méthode parler()
de la classe Animal
.
Utiliser super() pour appeler la méthode parente
Parfois, on veut ajouter un comportement tout en conservant celui de la classe parente. C’est là qu’intervient super()
:
class Chat(Animal):
def parler(self):
super().parler() # Appelle parler() d’Animal
print("Le chat miaule.")
super()
est très utile quand vous souhaitez enchaîner des appels à travers une hiérarchie de classes, notamment en héritage multiple.
Les classes imbriquées
En Python, on peut définir une classe à l’intérieur d’une autre. On parle alors de classe imbriquée.
Cela permet de regrouper des structures logiquement liées sous un même toit.
Voici un petit exemple avec une classe imbriquée :
class Voiture:
def __init__(self, marque):
self.marque = marque
class Moteur:
def __init__(self, type_moteur):
self.type_moteur = type_moteur
Et on utilise la classe imbriquée ainsi :
moteur = Voiture.Moteur("hybride")
print(moteur.type_moteur) # hybride
On peut par exemple utiliser une classe imbriquée quand la classe interne n'a pas de sens en dehors de la classe externe.
Évitez d'utiliser une classe imbriquée si vous avez besoin que cette dernière soit utilisée dans plusieurs contextes. De plus, utiliser trop de classes imbriquées peut parfois réduire fortement toute la lisibilité du projet. 😉
Les méthodes spéciales des classes
Les méthodes spéciales, aussi appelées "dunder methods 🇺🇸" (pour "double underscore"), sont des fonctions qui ont un nom encadré par deux underscores, comme __init__
, __str__
, etc.
Python les appelle automatiquement dans des contextes particuliers : création d'objet, impression, comparaison, etc.
Voici les méthodes plus fréquentes :
__init__()
-> appelée lors de la création d’une instance ;__str__()
-> utilisée parprint()
pour afficher un objet ;__repr__()
-> utilisée dans l’interpréteur pour représenter l’objet ;__eq__()
-> comparaison avec==
;__len__()
-> permet d’utiliserlen(obj)
.
Prenons cet exemple concret :
class Livre:
def __init__(self, titre, auteur):
self.titre = titre
self.auteur = auteur
def __str__(self):
return f"{self.titre} écrit par {self.auteur}"
l = Livre("1984", "George Orwell")
print(l) # 1984 écrit par George Orwell
Les méthodes spéciales permettent donc de rendre nos objets plus naturels à utiliser dans le langage Python.
Les méthodes de classe et les méthodes statiques
En Python, en plus des méthodes d’instance (qui utilisent self
comme nous l'avons vu 😋), nous pouvons définir deux autres types de méthodes au sein d'une classe :
- Les méthodes de classe (
@classmethod
) ; - Et les méthodes statiques (
@staticmethod
).
Les méthodes de classe
Elle reçoit la classe elle-même comme premier argument, nommé conventionnellement cls
.
Cela permet notamment de créer des constructeurs alternatifs ou des fonctions qui concernent la classe dans son ensemble (et non une instance en particulier).
Exemple :
class Utilisateur:
def __init__(self, nom):
self.nom = nom
@classmethod
def creer_invite(cls):
return cls("Invité")
invite = Utilisateur.creer_invite()
print(invite.nom) # Invité
Ici, creer_invite
est une méthode qui retourne une instance prédéfinie de la classe. Elle utilise cls
pour construire l’objet, ce qui la rend compatible même avec des sous-classes.
Utilisez
@classmethod
quand vous avez besoin de construire ou manipuler une classe sans passer par une instance.
Les méthodes statiques
Elle ne prend aucun argument implicite. C’est simplement une fonction rangée dans une classe par souci d'organisation.
Exemple :
class Maths:
@staticmethod
def carre(x):
return x * x
print(Maths.carre(5)) # 25
Dans cet exemple la méthode ne dépend ni d’un objet ni de la classe : c’est une fonction purement utilitaire, placée là pour regrouper la logique.
Tableau comparatif
Type de méthode | Paramètre principal | Accès à l'instance / classe | Usage |
Instance | self | Instance | Lire / Modifier les attributs |
Classe | cls | Classe | Constructeur alternatif (@classmethod ) |
Statique | Aucun | Aucun | Méthodes utilitaires (@staticmethod ) |
L'héritage multiple sur les classes
L’héritage multiple signifie qu’une classe peut hériter de plusieurs classes parentes à la fois. Cela donne de la flexibilité, mais demande aussi un peu plus d’attention. 😅
class Parle:
def parler(self):
print("Je parle.")
class Bouge:
def bouger(self):
print("Je bouge.")
class Robot(Parle, Bouge):
pass
r2d2 = Robot()
r2d2.parler() # Je parle.
r2d2.bouger() # Je bouge.
Ici, la classe Robot
hérite des méthodes des deux classes, sans aucune redéfinition. C’est simple et efficace.
Si deux classes parentes définissent une méthode du même nom, Python suit un ordre strict pour décider laquelle appeler : c’est le MRO, ou ordre de résolution des méthodes.
Exemple de conflit :
class A:
def afficher(self):
print("A")
class B:
def afficher(self):
print("B")
class C(A, B):
pass
c = C()
c.afficher() # A
Python appelle la méthode afficher()
de la classe la plus à gauche dans la déclaration (A
ici).
Vous pouvez afficher cet ordre avec la méthode __mro__
:
print(C.__mro__)
# (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
Composition vs héritage : quelle approche choisir ?
L’héritage n’est pas la seule manière de structurer un programme orienté objet.
Python propose aussi un autre principe fondamental : la composition, qui repose sur l'idée de "avoir un" au lieu de "être un" (c'est un peu tordu).
Qu'est-ce que la composition ?
La composition consiste à intégrer un objet dans un autre objet, plutôt qu’à hériter de sa classe.
Exemple :
class Moteur:
def demarrer(self):
print("Le moteur démarre.")
class Voiture:
def __init__(self):
self.moteur = Moteur()
def rouler(self):
self.moteur.demarrer()
print("La voiture roule.")
Ici, la classe Voiture
contient un objet de type Moteur
. Elle ne l’étend pas, mais l’utilise pour accomplir sa tâche.
Composition vs héritage : que choisir ?
Critère | Héritage | Composition |
Relation logique | "est un" (Un Chien est un Animal) | "a un" (Une voiture a un moteur) |
Couplage | Fort | Faible |
Flexibilité | Moindre | Élevée |
Réutilisation | Par héritage | Par délégation |
Utilisez l’héritage quand la relation est naturelle ("un carré est un rectangle")
Utilisez la composition quand vous assemblez des fonctionnalités ("une voiture a un GPS").
Facile, non ? 👀
Typage et annotations des classes
Python permet d’annoter les types des attributs et des paramètres pour plus de clarté, notamment avec l’aide d’outils comme mypy, pylance ou des IDE comme PyCharm.
Voici un exemple de typage dans une classe :
class Produit:
nom: str
prix: float
def __init__(self, nom: str, prix: float):
self.nom = nom
self.prix = prix
Grâce à ces annotations, on sait immédiatement à quoi s’attendre et les outils de développement peuvent vérifier automatiquement la cohérence des types.
On précise par exemple :
- Que
nom
est une chaîne de caractère (str
) ; - Que
prix
est un flottant (float
) - donc un nombre à virgule.
On peut également typer les méthodes :
def calculer_tva(self, taux: float) -> float:
return self.prix * taux
Ici taux
s'attend à recevoir également un flottant, calculer_tva
doit retourner un flottant aussi (-> float
) !
Finalement, les annotations ne changent pas le fonctionnement du programme (elles sont non obligatoires à l’exécution), mais elles renforcent la documentation et la maintenabilité.
Les dataclasses, une alternative pour les classes
Introduites en Python 3.7 via le module dataclasses
, les dataclasses offrent une manière simple, propre et concise de créer des classes contenant surtout des données.
Créer une classe classique avec attributs, constructeur, méthodes spéciales (__repr__
, __eq__
, etc.) peut être verbeux.
Les dataclasses font tout cela automatiquement ! 👀
from dataclasses import dataclass
@dataclass
class Livre:
titre: str
auteur: str
pages: int
livre1 = Livre("1984", "Orwell", 328)
print(livre1)
# Livre(titre='1984', auteur='Orwell', pages=328)
Pas besoin d’écrire __init__
, __repr__
ou __eq__
: tout est généré automatiquement.
Mais ce n'est pas tout, il est possible d'ajouter des options pour rendre une classe immuable ou pour la trier automatiquement :
@dataclass(frozen=True, order=True)
class Produit:
nom: str
prix: float
Dans cet exemple :
frozen=True
rend l’objet immuable (comme un tuple) ;order=True
permet de trier les objets (<
,>
, etc.).
Les classes et les exceptions
Créer nos propres exceptions personnalisées nous permet de contrôler finement les erreurs dans un code orienté objet, tout en gardant des messages clairs.
Voici un exemple d'exception :
class SoldeInsuffisantError(Exception):
"""Exception levée quand le solde est insuffisant."""
pass
Et voici comment l'intégrer dans une classe :
class Compte:
def __init__(self, solde):
self.solde = solde
def retirer(self, montant):
if montant > self.solde:
raise SoldeInsuffisantError("Fonds insuffisants.")
self.solde -= montant
En créant nos propres exceptions, on facilite le débogage et la compréhension du code.
Questions fréquentes sur les classes
Quand on commence avec les classes (et même après !) on peut se poser de nombreuses questions... Essayons de voir les plus fréquentes d'entre-elles.
À quoi sert
self
dans une classe ?
self
est une référence à l’instance courante d’un objet. Il permet d’accéder à ses attributs et d’appeler ses méthodes depuis l’intérieur de la classe.
Faut-il toujours utiliser
__init__
?
Pas toujours ! Si vous utilisez une @dataclass
, Python crée __init__
pour vous.
Sinon, __init__
est indispensable pour initialiser les attributs.
Peut-on avoir plusieurs classes dans un même fichier ?
Oui, et c’est courant. Mais veillez à ce que le fichier reste lisible : une classe principale par fichier est une bonne pratique si le projet devient complexe.
Comment apprendre Python ?
Avec une formation complète.