Module : Méthodes de Classe et Méthodes Statiques
1. Quoi : Au-delà des méthodes d'instance
En plus des méthodes d'instance (celles qui prennent self comme premier argument), les classes Python peuvent avoir deux autres types de méthodes : les méthodes de classe et les méthodes statiques.
-
Méthode d'instance :
def my_method(self, ...)- Opère sur une instance spécifique (
self). - Peut accéder et modifier les attributs de l'instance (
self.name). - C'est le type de méthode le plus courant.
- Opère sur une instance spécifique (
-
Méthode de classe :
@classmethod def my_method(cls, ...)- Opère sur la classe elle-même (
cls), pas sur une instance. - Peut accéder et modifier les attributs de la classe (
cls.species). - Ne peut pas accéder aux attributs d'une instance spécifique (
self).
- Opère sur la classe elle-même (
-
Méthode statique :
@staticmethod def my_method(...)- N'opère ni sur l'instance (
self) ni sur la classe (cls). - Est essentiellement une fonction normale, mais "rangée" à l'intérieur de la classe pour des raisons d'organisation.
- Ne peut accéder ni aux attributs de l'instance, ni aux attributs de la classe.
- N'opère ni sur l'instance (
2. Pourquoi : Différents niveaux d'opération
Le choix entre ces types de méthodes dépend de ce sur quoi la méthode doit opérer.
- Utilisez une méthode d'instance pour tout ce qui concerne un objet spécifique (ex:
my_car.drive()). - Utilisez une méthode de classe lorsque la méthode est liée à la classe dans son ensemble, mais pas à une instance particulière. Un cas d'usage très courant est la création de constructeurs alternatifs.
- Utilisez une méthode statique pour une fonction utilitaire qui est logiquement liée à la classe, mais qui n'a pas besoin d'accéder à l'état de la classe ou de l'instance.
3. Comment : Les Décorateurs @classmethod et @staticmethod
A. Méthodes de Classe (@classmethod)
On utilise le décorateur @classmethod. Le premier argument de la méthode est, par convention, cls (qui représente la classe).
Exemple : Un constructeur alternatif
Imaginons que nous avons une classe Person et que nous voulons pouvoir la créer à partir d'une année de naissance, en plus du constructeur standard qui prend l'âge.
import datetime
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_birth_year(cls, name, birth_year):
"""
Constructeur alternatif pour créer une personne à partir de son année de naissance.
'cls' ici est la classe Person elle-même.
"""
current_year = datetime.date.today().year
age = current_year - birth_year
# On retourne une nouvelle instance de la classe 'cls'
return cls(name, age)
def display_info(self):
print(f"{self.name} is {self.age} years old.")
# Utilisation du constructeur standard
person1 = Person("Alice", 30)
# Utilisation du constructeur alternatif (méthode de classe)
person2 = Person.from_birth_year("Bob", 1995)
person1.display_info() # Alice is 30 years old.
person2.display_info() # Bob is 30 years old. (en 2025)
L'avantage d'utiliser cls au lieu de Person directement dans la méthode est que si une autre classe hérite de Person, cette méthode de classe créera une instance de la classe enfant, pas de Person.
B. Méthodes Statiques (@staticmethod)
On utilise le décorateur @staticmethod. La méthode ne prend ni self ni cls comme premier argument implicite.
Exemple : Une fonction utilitaire
Imaginons une classe MathUtils qui regroupe des fonctions mathématiques. Ces fonctions n'ont pas besoin de l'état d'une instance ou de la classe.
class MathUtils:
@staticmethod
def add(a, b):
"""Une fonction simple qui n'a pas besoin de self ou cls."""
return a + b
@staticmethod
def is_even(number):
"""Vérifie si un nombre est pair."""
return number % 2 == 0
# On peut appeler la méthode statique directement depuis la classe
result = MathUtils.add(5, 3)
print(result) # 8
print(MathUtils.is_even(10)) # True
print(MathUtils.is_even(7)) # False
Question : Pourquoi ne pas simplement définir is_even comme une fonction normale en dehors de la classe ?
Réponse : On le pourrait. Mais la placer à l'intérieur de la classe MathUtils a du sens d'un point de vue de l'organisation et de l'espace de noms (namespace). Cela indique clairement que is_even est une fonctionnalité liée au concept de MathUtils.
4. Résumé
| Type de Méthode | Décorateur | Premier Argument | Accède à self (instance) ? | Accède à cls (classe) ? | Objectif Principal |
|---|---|---|---|---|---|
| Méthode d'Instance | (aucun) | self | ✅ Oui | ✅ Oui | Opérer sur l'état d'une instance spécifique. |
| Méthode de Classe | @classmethod | cls | ❌ Non | ✅ Oui | Opérer sur l'état de la classe, constructeurs alternatifs. |
| Méthode Statique | @staticmethod | (aucun) | ❌ Non | ❌ Non | Fonction utilitaire logiquement liée à la classe. |
Exercice 01 : Validateur de Date
Objectif
Cet exercice a pour but de vous faire utiliser des méthodes de classe et des méthodes statiques dans un contexte pratique : la création et la validation d'une classe Date.
Contexte
Vous allez créer une classe Date qui stocke un jour, un mois et une année.
- Le constructeur principal (
__init__) prendra le jour, le mois et l'année comme entiers. - Vous créerez un constructeur alternatif (une méthode de classe) pour créer une instance de
Dateà partir d'une chaîne de caractères au format "DD-MM-YYYY". - Vous ajouterez une méthode statique comme fonction utilitaire pour vérifier si une année est bissextile, car cette logique est liée aux dates mais n'a pas besoin d'une instance ou de la classe
Dateelle-même pour fonctionner.
Énoncé
-
Créez un nouveau fichier Python nommé
date_validator.py. -
Définissez la classe
Date.- Son constructeur
__init__doit accepterday,month, etyearet les stocker. - Implémentez la méthode
__str__pour qu'elle retourne la date au format "DD/MM/YYYY".- Pour afficher le numéro du jour et du mois sur 2 chiffres, vous pouvez utiliser
:02d(self.month:02d)
- Pour afficher le numéro du jour et du mois sur 2 chiffres, vous pouvez utiliser
- Son constructeur
-
Créez une méthode statique
is_leap_year.- Décorez-la avec
@staticmethod. - Elle prend un seul argument,
year. - Elle doit retourner
Truesi l'année est bissextile,Falsesinon. - Rappel de la logique d'une année bissextile : une année est bissextile si elle est divisible par 4, sauf si elle est divisible par 100, à moins qu'elle ne soit également divisible par 400.
- Décorez-la avec
-
Créez une méthode de classe
from_string.-
Décorez-la avec
@classmethod. -
Elle prend deux arguments :
clsetdate_string. -
Elle doit "parser" la
date_string(qui est au format "DD-MM-YYYY") pour en extraire le jour, le mois et l'année.- Astuce : Utilisez la méthode
.split('-')sur la chaîne.
- Astuce : Utilisez la méthode
-
Elle doit convertir ces parties en entiers.
- Astuce : Utilisez la méthode
map(int, ...)pour insérer les valeurs d'une liste dans des variables.
- Astuce : Utilisez la méthode
-
Elle doit ensuite retourner une nouvelle instance de la classe en utilisant
cls(day, month, year).
-
-
Testez votre classe :
- Créez une instance de
Dateen utilisant le constructeur normal. - Créez une autre instance en utilisant la méthode de classe
from_string. - Affichez les deux dates pour vérifier que la méthode
__str__fonctionne. - Testez la méthode statique
is_leap_yearavec quelques années (ex: 2020, 2021, 1900, 2000).
- Créez une instance de
Résultat Attendu
Date 1: 25/12/2023
Date 2: 30/10/2024
Is 2020 a leap year? True
Is 2021 a leap year? False
Is 1900 a leap year? False
Is 2000 a leap year? True
Cliquez ici pour voir un exemple de code de solution
# date_validator.py
class Date:
def __init__(self, day, month, year):
self.day = day
self.month = month
self.year = year
def __str__(self):
return f"{self.day:02d}/{self.month:02d}/{self.year}"
@staticmethod
def is_leap_year(year):
"""Checks if a year is a leap year."""
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
@classmethod
def from_string(cls, date_string):
"""Creates a Date instance from a 'DD-MM-YYYY' string."""
day, month, year = map(int, date_string.split('-'))
return cls(day, month, year)
# --- Testing ---
# 1. Using the standard constructor
date1 = Date(25, 12, 2023)
print(f"Date 1: {date1}")
# 2. Using the class method as an alternative constructor
date_str = "30-10-2024"
date2 = Date.from_string(date_str)
print(f"Date 2: {date2}")
# 3. Using the static method
print(f"Is 2020 a leap year? {Date.is_leap_year(2020)}")
print(f"Is 2021 a leap year? {Date.is_leap_year(2021)}")
print(f"Is 1900 a leap year? {Date.is_leap_year(1900)}")
print(f"Is 2000 a leap year? {Date.is_leap_year(2000)}")