ProgrammationDéveloppeur Backend Python

Qu'est-ce que les décorateurs de fonctions en Python, quelle est leur histoire et pourquoi sont-ils utilisés dans la programmation moderne ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse

Histoire de la question

Les décorateurs sont apparus dans Python comme un sucre syntaxique à partir de la version 2.4 pour faciliter le travail avec des fonctions de premier ordre - celles qui acceptent ou retournent d'autres fonctions. L'évolution des approches pour étendre la fonctionnalité des fonctions a conduit à un format concis et expressif - des annotations via @decorator.

Problème

Dans les grands projets, il est souvent nécessaire de modifier ou d'entourer des fonctions avec une logique supplémentaire : journalisation, vérification d'accès, mise en cache, mesure du temps d'exécution. Sans les décorateurs, il fallait appeler manuellement les fonctions enveloppantes, ce qui gonflait le code.

Solution

Les décorateurs permettent d'extraire des aspects répétitifs dans des enveloppes distinctes, améliorant ainsi la lisibilité et la réutilisabilité du code. Avec @decorator, on peut ajouter élégamment des fonctionnalités aux fonctions et méthodes :

Exemple de code :

import time def timing_decorator(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(f'Elapsed: {time.time() - start:.3f}s') return result return wrapper @timing_decorator def slow_function(): time.sleep(0.5) slow_function() # Affiche combien de temps a pris l'exécution

Caractéristiques clés :

  • Permettent de réutiliser du code (principe DRY);
  • Peuvent être utilisées pour des fonctions, méthodes, classes;
  • Permettent d'intégrer des tâches transversales (journalisation, cache, accès, profilage) de manière centralisée;

Questions pièges.

Les décorateurs peuvent-ils modifier la signature de la fonction enveloppée ?

On pense souvent à tort que la signature de la fonction par le décorateur ne change pas. En réalité, sans l'utilisation du module functools.wraps, les métadonnées disparaissent, ce qui peut provoquer des erreurs inattendues dans la documentation automatique ou l'introspection.

Exemple de code :

from functools import wraps def decorator(func): @wraps(func) def wrapper(*args): return func(*args) return wrapper

Les décorateurs peuvent-ils être paramétrés ?

On répond souvent que non. En réalité, il est possible de créer un décorateur paramétré via un niveau d'imbrication supplémentaire - une fonction qui retourne un décorateur.

Exemple de code :

def repeat(n): def decorator(func): def wrapper(*args, **kwargs): result = None for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def hello(): print("Hello!")

Peut-on appliquer plusieurs décorateurs à une même fonction ? Quel sera l'ordre d'exécution ?

On pense souvent à tort que l'ordre n'a pas d'importance ou qu'il correspond à l'ordre de placement des décorateurs dans le code. En réalité, le décorateur le plus bas est appliqué en premier, puis le suivant, et ainsi de suite vers le haut.

Exemple de code :

def dec1(f): def wrapper(*a, **k): print("dec1") return f(*a, **k) return wrapper def dec2(f): def wrapper(*a, **k): print("dec2") return f(*a, **k) return wrapper @dec1 @dec2 def f(): print("core") f() # dec1, dec2, core

Erreurs typiques et anti-schémas

  • Ignorer functools.wraps perd des métadonnées sur la fonction originale;
  • La logique du décorateur ne prend pas en compte les exceptions, impossible de capturer et de traiter une erreur à l'intérieur ;
  • Chevauchement non évident de plusieurs décorateurs.

Exemple tiré de la vie réelle

Cas négatif

La journalisation du temps d'exécution a été ajoutée manuellement à 10 fonctions par copie de code.

Avantages :

  • La logique est claire, le code est proche, facile de trouver l'erreur.

Inconvénients :

  • Difficile à maintenir — il faudra modifier des dizaines de parties si l'on doit changer le comportement.
  • Le code est dupliqué, ce qui viole le principe DRY.

Cas positif

Toute la logique de chronométrage a été extraites dans un décorateur, toutes les fonctions nécessitant cette métrique sont enveloppées avec le décorateur @timing_decorator.

Avantages :

  • Les changements sont effectués de manière centralisée ;
  • Le code est plus court et plus lisible.

Inconvénients :

  • Possibilité de perte d'informations sur la signature sans functools.wraps, si on n'est pas attentif ;
  • Pour les débutants, il peut être plus difficile de comprendre immédiatement le mécanisme des décorateurs.