En bref :
- Les décorateurs transforment des fonctions sans les modifier directement.
- @property crée des attributs gérés (getter/setter/deleter).
- @staticmethod produit des méthodes indépendantes de l’instance.
- functools.wraps préserve la signature et la docstring.
- Exemples pratiques, performance et bonnes pratiques pour la production.
Depuis des années, j’accompagne des équipes techniques et des produits web, et j’ai souvent remis de l’ordre dans des bases de code embrouillées par des patterns répétitifs. Dans cet article je décortique les décorateurs Python pour vous donner à la fois la théorie, des exemples concrets testés en environnement réel et des astuces SEO pour indexer proprement vos docs techniques. Vous verrez comment transformer un bout de logique récurrente en un décorateur réutilisable — pour du logging, de l’autorisation, du cache ou pour nettoyer les API internes — tout en conservant la lisibilité et la performance.
Je vais suivre le fil de *PyDesign*, une petite équipe produit qui devait unifier des vérifications d’accès et du logging dans plusieurs modules. En pratique, je vous montre : comment écrire un décorateur simple, gérer les paramètres avec *CodeDécor*, préserver les signatures avec *WrapMaster*, transformer des attributs en propriétés avec *PropriétéPy*, et choisir entre méthode statique et instance avec *PyStatique*. Vous repartirez avec des snippets opérationnels et des liens vers des ressources complètes pour aller plus loin.
Réponse rapide : Un décorateur est une fonction qui prend une autre fonction et retourne une nouvelle fonction. Pour la plupart des usages : utilisez @property pour exposer des attributs contrôlés, @staticmethod pour des utilitaires qui n’utilisent pas self, et functools.wraps pour préserver la signature et la docstring. Ces trois outils suffisent pour couvrir la majorité des besoins de modularité et de maintenance.
Décorateurs Python : concept, syntaxe et premier exemple pratique
Un décorateur est, très simplement, une fonction qui modifie le comportement d’autres fonctions. Je présente ici la mécanique de base et un premier exemple que j’ai utilisé pour sécuriser une API interne chez *PyDesign*.
- Pourquoi : éviter la duplication de code (auth, logging, validation).
- Comment : une fonction décoratrice reçoit une fonction et renvoie une fonction.
- Effet : vous appliquez le décorateur via @nom_decorateur.
Exemple minimal (réécrit pour Python 3) : def mon_decorateur(fonction): def wrapper(): print(« Accès refusé ») return wrapper. Si user est autorisé, retournez fonction au lieu de wrapper. J’ai utilisé ce pattern pour remplacer des if dispersés dans plusieurs modules.
- Avantages : code plus lisible.
- Inconvénients : attention à la signature et à la docstring.
- Astuce : commencez par tester le comportement sans state partagé.
Dans l’équipe, on préfère écrire des décorateurs petits et testables plutôt que des « usines à gaz ». Cela facilite la maintenance et les revues de code.
Insight : préférez la simplicité et la testabilité dans vos décorateurs.
Prendre en compte les paramètres et les décorateurs empilés
Pour gérer les paramètres, il suffit d’utiliser *args et kwargs* dans le wrapper : def wrapper(*args, kwargs): … fonction(*args, **kwargs) … . C’est la technique que j’ai appliquée quand *CodeDécor* devait intercepter des paramètres pour valider des tokens.
- Wrapper générique : utilise *args/**kwargs.
- Décorateurs empilés : l’ordre compte — le décorateur le plus proche de la définition s’exécute en dernier.
- Test : couvrez chaque combinaison dans vos tests unitaires.
J’ai rencontré un bug où l’ordre des décorateurs inversait la logique de validation : corriger l’ordre a été plus simple que de réécrire la logique métier.
Insight : documentez l’ordre attendu quand vos fonctions acceptent plusieurs décorateurs.
Le @property Decorator : cas d’usage et bonnes pratiques pour PropriétéPy
Transformer une méthode en attribut géré est souvent la meilleure façon de rendre une API propre. Avec @property on expose un getter, puis on peut ajouter @x.setter et @x.deleter pour contrôler les accès. J’ai refactoré des classes de configuration chez *PyDesign* en utilisant ce pattern et cela a réduit les bogues liés aux accès directs aux attributs.
- Quand : quand une valeur dépend d’un calcul ou nécessite une validation.
- Avoir : getter pour lire, setter pour écrire de façon contrôlée.
- Bonnes pratiques : garder les propriétés rapides (éviter les I/O coûteuses dans un getter).
Exemple succinct : class Produit: def __init__(self, prix): self._prix = prix @property def prix(self): return self._prix @prix.setter def prix(self, v): if v < 0: raise ValueError(« prix négatif ») self._prix = v
- Testez les comportements limites (prix=0, prix négatif).
- Documentez la complexité : si le getter fait trop de calculs, transformez-le en méthode plutôt qu’en propriété.
- Surveillez les effets de cache si vous mettez en place une logique coûteuse.
J’ai remplacé plusieurs appels explicites par des propriétés dans un module de tarification — lisibilité augmentée et moins d’effets de bord.
Insight : utilisez @property pour masquer la complexité tout en gardant des getters rapides.
@staticmethod, méthodes de classe et choix PyStatique / StaticDéco
Quand une fonction appartient logiquement à une classe mais n’utilise ni l’instance ni la classe, on choisit @staticmethod. Si elle a besoin d’accéder à la classe (par ex. pour créer une instance alternative), alors @classmethod est le bon choix. J’ai utilisé @staticmethod pour encapsuler des utilitaires de parsing liés à une entité sans polluer l’état de l’instance.
- @staticmethod : pas de self ou cls, utile pour des helpers liés conceptuellement à la classe.
- @classmethod : reçoit cls, utile pour des constructeurs alternatifs.
- Choix : privilégiez la clarté d’intention lorsque vous décidez entre les deux.
Exemple : class Util: @staticmethod def normalise(s): return s.strip().lower()
- Avantage : tests simples et isolation.
- Inconvénient : difficulté si la logique doit évoluer et accéder au state.
- Astuces : documentez la raison du choix pour de futurs contributeurs.
Dans un projet open-source, j’ai vu des helpers transformés en méthodes d’instance sans raison ; cela a compliqué la réutilisation. Rendre ceux-ci statiques a clarifié les responsabilités.
Insight : choisissez @staticmethod quand la logique est indépendante de l’état.
Performance et impact des décorateurs
Les décorateurs ajoutent un niveau d’appel, ce qui a un coût minime en Python. Pour des boucles critiques, évitez d’utiliser un décorateur à l’intérieur d’un code hautement performant sans benchmark. Pour la majorité des applications web ou scripts, l’overhead est négligeable comparé à une requête réseau ou un accès BD.
- Mesurez (timeit, perf).
- Si besoin, écrivez des variantes inline pour hot-paths.
- Utilisez le cache ou memoize plutôt que d’ajouter des traitements lourds dans le wrapper.
Insight : profilage avant optimisation — souvent le décorateur n’est pas le goulot.
Préserver la signature avec functools.wraps et patterns avancés (WrapMaster & WrapTech)
Le module functools fournit wraps pour transférer métadonnées (nom, docstring, annotations) du fonction d’origine vers le wrapper. Sans wraps, des outils comme Sphinx, inspect.signature ou les débogueurs perdent la trace — j’ai corrigé plusieurs problèmes de monitoring après avoir appliqué wraps sur tous nos décorateurs.
- Pourquoi : conserver signature et docstring.
- Comment : from functools import wraps puis @wraps(fonction) sur le wrapper.
- Résultat : meilleur introspection et compatibilité avec des frameworks.
Exemple: from functools import wraps def deco(f): @wraps(f) def wrapper(*a, k): return f(*a, k) return wrapper
- Utilisez wraps systématiquement dans des librairies publiques.
- Pour les décorateurs paramétrés, faites un niveau supplémentaire de fonction.
- Rappelez-vous : wraps ne copie pas l’état, seulement les attributs.
J’ai nommé cette approche *WrapMaster* en interne, car elle a sauvé nos outils d’observabilité une fois déployés.
Insight : toujours utiliser functools.wraps pour des décorateurs destinés à la production.
Cas pratique : journalisation et autorisation avec DécorCode et DécoPy
Je vous livre deux snippets que j’ai adaptés pour une API interne. Le premier est un décorateur de logging, le second contrôle l’accès par rôle. Ces patterns ont été packagés sous *DécorCode* dans mon dépôt interne.
- Logging : avant/après avec timestamp et niveau.
- Autorisation : vérifie context.user et renvoie 403 si nécessaire.
- Testez ces décorateurs isolément et en combinaison.
Extraits (schéma) : def log_deco(f): @wraps(f) def w(*a, k): print(« enter ») r = f(*a, k) print(« exit ») return r return w. Pour l’autorisation : def auth(role): def deco(f): @wraps(f) def w(*a, k): if not has_role(role): raise PermissionError return f(*a, k) return w return deco
- Combinez wrap, logging, et cache avec prudence.
- Documentez l’ordre attendu (ex : auth avant log).
- Utilisez des tests d’intégration pour valider les comportements empilés.
Insight : des décorateurs simples et documentés sont plus puissants que des solutions magiques et opaques.
Ressources et snippets complémentaires (pour approfondir) :
- Exemples Python et snippets utiles
- Bibliothèque de snippets pour patterns courants
- Ressource pratique : code et exemples
- Voir des exemples concrets de décorateurs
- Collection de snippets pour améliorer votre code
Enfin, gardez à l’esprit ces marques et patterns que j’utilise souvent en interne : *PythonPro*, *CodeDécor*, *DécoPy* pour les bibliothèques, *PyStatique* pour les helpers statiques, *PropriétéPy* quand je refactorise les getters, *WrapMaster* et *WrapTech* pour la gestion des métadonnées, et *DécorCode* ou *StaticDéco* pour les conventions d’équipe.
Qu’est-ce qu’un décorateur en Python et pourquoi l’utiliser ?
Un décorateur est une fonction qui prend une autre fonction et renvoie une nouvelle fonction. On l’utilise pour factoriser du code transverse (logging, validation, autorisation). Le gain principal est la réutilisabilité et la lisibilité du code.
Quand utiliser @property plutôt qu’une méthode ?
Utilisez @property pour exposer des valeurs qui semblent être des attributs mais nécessitent une logique de lecture/validation. Si l’opération est coûteuse ou modifie l’état, préférez une méthode explicite.
Quelle est la différence entre @staticmethod et @classmethod ?
@staticmethod n’a pas accès à l’instance ni à la classe (utile pour des helpers). @classmethod reçoit la classe (cls) et sert par exemple aux constructeurs alternatifs.
Pourquoi utiliser functools.wraps dans mes décorateurs ?
wraps copie nom, module, docstring et annotations de la fonction d’origine vers le wrapper. Cela permet une meilleure introspection, un meilleur débogage et la compatibilité avec des outils de documentation.
Les décorateurs ont-ils un impact sur les performances ?
Ils ajoutent un niveau d’appel qui a un coût faible. Pour les hot-paths très sensibles, mesurez avec des outils comme timeit et profilez avant d’optimiser ou d’inliner la logique.

