Skip to main content
Niveau : Avancé

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.
  • 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).
  • 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.

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éthodeDécorateurPremier ArgumentAccède à self (instance) ?Accède à cls (classe) ?Objectif Principal
Méthode d'Instance(aucun)self✅ Oui✅ OuiOpérer sur l'état d'une instance spécifique.
Méthode de Classe@classmethodcls❌ Non✅ OuiOpérer sur l'état de la classe, constructeurs alternatifs.
Méthode Statique@staticmethod(aucun)❌ Non❌ NonFonction 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 Date elle-même pour fonctionner.

Énoncé

  1. Créez un nouveau fichier Python nommé date_validator.py.

  2. Définissez la classe Date.

    • Son constructeur __init__ doit accepter day, month, et year et 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)
  3. Créez une méthode statique is_leap_year.

    • Décorez-la avec @staticmethod.
    • Elle prend un seul argument, year.
    • Elle doit retourner True si l'année est bissextile, False sinon.
    • 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.
  4. Créez une méthode de classe from_string.

    • Décorez-la avec @classmethod.

    • Elle prend deux arguments : cls et date_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.
    • 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.
    • Elle doit ensuite retourner une nouvelle instance de la classe en utilisant cls(day, month, year).

  5. Testez votre classe :

    • Créez une instance de Date en 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_year avec quelques années (ex: 2020, 2021, 1900, 2000).

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)}")