Définition
Une fonction écrite dans une classe reçoit d'habitude l'objet sur lequel elle travaille, via self. Mais certaines fonctions n'ont besoin de rien de tout cela : elles transforment leurs arguments, sans jamais interroger l'objet ni la classe. Leur donner un paramètre qui ne sert jamais serait absurde, c'est ce que @staticmethod permet d'éviter.
@staticmethod est un décorateur qui range une fonction ordinaire dans une classe, sans self ni cls. Voici à quoi cela ressemble sur une fonction de validation :
class Facture:
TAUX = 0.20
@staticmethod
def numero_valide(numero):
return len(numero) == 8 and numero.isdigit()
Facture.numero_valide("20260819") # TrueL'appel fonctionne aussi bien sur la classe que sur une instance : la fonction ne recevant rien en coulisses, elle reçoit toujours exactement les mêmes arguments, peu importe par où on l'appelle.
Les trois sortes de méthodes
Python propose trois façons d'écrire une méthode, et le choix entre elles tient à une seule question : de quoi cette méthode a-t-elle besoin pour travailler ?
Le tableau ci-dessous résume les trois réponses, et ce que chacune reçoit automatiquement en premier argument.
| Écriture | Premier paramètre reçu | À choisir quand |
|---|---|---|
| Méthode d'instance | self, l'objet | Le travail dépend des données de l'objet |
classmethod | cls, la classe | Le travail dépend de la classe, pas de l'objet |
@staticmethod | Rien du tout | Le travail ne dépend que des arguments reçus |
La méthode statique est la seule des trois dont la signature ne cache rien : ce que vous écrivez entre les parenthèses est exactement ce qu'elle reçoit. Vous pouvez donc la tester isolément, sans construire d'objet ni préparer le moindre __init__.
La différence se voit aussi, un peu, côté performance : une méthode d'instance construit à chaque appel un petit objet intermédiaire, la méthode liée, qu'une méthode statique s'épargne.
Quand elle se justifie
Ranger une fonction dans une classe n'est jamais obligatoire. La question à se poser : pourquoi ne pas l'écrire à côté, comme une fonction indépendante ? Le bon cas est celui d'une fonction utilitaire qui appartient au vocabulaire de la classe sans toucher à son état : une validation, une conversion, un calcul que rien d'autre n'utilise. La ranger dans la classe la place là où le prochain lecteur ira la chercher, plutôt que dans le module, parmi vingt fonctions sans rapport.
La classe suivante montre deux méthodes qui s'y côtoient sans se gêner.
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
@staticmethod
def fahrenheit_vers_celsius(valeur):
return (valeur - 32) * 5 / 9
@classmethod
def depuis_fahrenheit(cls, valeur):
return cls(cls.fahrenheit_vers_celsius(valeur))La conversion ne dépend d'aucun objet : c'est une formule, elle reste statique. Le constructeur de remplacement, lui, a besoin de la classe pour fabriquer l'instance, donc il prend cls. Les deux cohabitent bien : chacune reçoit exactement ce dont elle a besoin.
Le piège du décorateur oublié
C'est l'erreur la plus fréquente sur le sujet, et elle se déclenche tard, une fois le code déjà écrit. Sans l'arobase, Python traite la fonction comme une méthode d'instance ordinaire et glisse l'objet en premier argument à chaque appel.
La conséquence dépend alors de la façon dont on appelle la méthode.
class Facture:
def numero_valide(numero): # décorateur oublié
return len(numero) == 8
Facture.numero_valide("20260819") # passe
facture = Facture()
facture.numero_valide("20260819")
# TypeError: numero_valide() takes 1 positional argument but 2 were givenL'appel depuis la classe passe sans réagir, aucun objet ne s'invitant dans les arguments. C'est l'appel depuis une instance qui échoue, avec une TypeError annonçant un argument de trop qu'on ne voit nulle part dans son propre code : c'est l'instance, transmise en silence. Ce décalage d'exactement un argument entre ce que la méthode déclare et ce qu'elle reçoit est la signature la plus fiable d'un décorateur manquant.
Statique et héritage
Une méthode statique s'hérite comme le reste, et une classe fille peut la remplacer. Mais elle ne reçoit jamais ni self ni cls : si son code nomme une classe en dur, cette classe reste la même, quelle que soit la fille depuis laquelle on l'appelle.
class Produit:
TAUX = 0.20
@staticmethod
def avec_taxe(montant):
return montant * (1 + Produit.TAUX) # figé sur Produit
class Livre(Produit):
TAUX = 0.055
Livre.avec_taxe(100) # 120.0, alors que 105.5 était attenduLivre.avec_taxe(100) renvoie 120.0 plutôt que les 105.5 attendus : la méthode ignore que Livre existe, elle ne connaît que Produit, écrit en dur dans son corps. Dès qu'un attribut de classe entre dans le calcul, la méthode n'est plus statique par nature : c'est un classmethod qu'il faut écrire, pour que cls désigne la bonne classe. Repère utile : si le corps d'une méthode statique prononce le nom de sa propre classe, méfiez-vous.
Questions fréquentes
Pourquoi ne pas écrire une simple fonction en dehors de la classe ?
Rien ne l'interdit, et c'est souvent le meilleur choix : une classe encombrée de fonctions sans rapport avec elle devient pénible à lire. Le critère est le vocabulaire du domaine modélisé. Si la fonction n'a de sens que rapportée à la classe, comme valider un numéro de facture, la ranger dedans documente ce lien. Si elle sert ailleurs, elle appartient au module. Une méthode statique que personne n'appelle jamais par le nom de sa classe s'est probablement trompée d'endroit.
Quelle différence exacte avec classmethod ?
Une méthode de classe reçoit la classe en premier argument, une méthode statique ne reçoit rien. La conséquence est concrète : la première suit la classe réellement appelée, même depuis une fille, la seconde reste figée sur ce que son code nomme explicitement, comme avec Livre et Produit. Dès qu'une classe fille doit changer le résultat, la version statique devient un piège silencieux.
Peut-on l'appeler depuis le corps de la classe ?
Depuis Python 3.10, oui : l'objet produit par le décorateur est devenu appelable directement, ce qui n'était pas le cas avant. Sur les versions antérieures, la même écriture lève un message déroutant, 'staticmethod' object is not callable, et impose de passer par .__func__ pour récupérer la fonction d'origine.