Written by @jefcolbi, 2024-04-27, Last update: 2024-04-27
Bonjour le monde,
Nous allons voir ce que sont les décorateurs Python, comment on les ecrit et à quoi ils servent dans la pratique.
C’est bien de découvrir une fonctionnalité ou un concept mais c’est meilleur de savoir à quoi ça peut servir. Découvrons donc 3 cas pratiques où les décorateurs peuvent servir. Ensuite nous verrons les definitions linguistique et programmatique.
Un singleton est une classe qui ne doit être instanciée qu’une seule fois. Pourquoi? Pour diverses raisons, en géneral la classe en question gère l’accès à certaines ressources et on aimerait pas avoir plusieurs gestionnaires dans un même programme. Voici donc un code qui permet de déclarer un singleton.
import functools
existing_instances = {}
def singleton(cls):
"""Transformer une classe en singleton (une seul instance)"""
@functools.wraps(cls)
def wrapper_singleton(*args, **kwargs):
if cls.__name__ not in existing_instances:
existing_instances[cls.__name__] = cls(*args, **kwargs)
return existing_instances[cls.__name__]
return wrapper_singleton
@singleton
class MaClasse:
pass
Personnellement je n’utilise pas cette methode pour faire un singleton, je prefère redefinir la methode spéciale __new__()
. Quoique ce n’est pas évident.
Parfois on voudrait empecher l’accès à certaines fonctions ou classes pendant l’execution du programme dépendamment du contexte. Par exemple sur Django, si on veut empecher un utilisateur d’avoir accès à une page s’il n’est pas connecté on utilise le décorateur @login_required. Justement regardons son code source
def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
"""
Decorator for views that checks that the user is logged in, redirecting
to the log-in page if necessary.
"""
actual_decorator = user_passes_test(
lambda u: u.is_authenticated,
login_url=login_url,
redirect_field_name=redirect_field_name
)
if function:
return actual_decorator(function)
return actual_decorator
Avec ce code aussi simple, on peut limiter l’accès à certaines vues. Cela par opposition à la méthode manuelle que les nouveaux font qui consiste à faire:
def my_view(request):
if request.user.is_authenticated:
return redirect(reverse('login'))
...
Quoique cette méthode fonctionne, elle manque de flexibilité parce qu’elle exige de refaire la meme chose dans toutes les vues qu’on voudrait proteger.
Supposons que vous developpez un gros projet et à un niveau vous avez besoin d’enregister et utiliser un ensemble d’entités toutes différentes mais partageant un même point commun comme par exemple la même signature, le même type de retour ou encore la même interface (méthode commune). Mais vous ne connaissez pas la quantité d’entités qui seront crées. Ça peut être une seule, voir 3 voir même 100. A ce stade, vous avez besoin d’un moyen d’enregistrer les entités à l’exécution (runtime registering) et les décorateurs le font à merveille.
L’une des choses que les développeurs Flask aiment c’est la simplixité avec laquelle on associe les vues aux endpoints. A la difference de Django où il faut créer une liste d’instances d’Url avec Flask il suffit d’utiliser le décorateur @route
présent dans une instance de la classe Flask. Le décorateur en réalité est un méthode de la classe Scaffold https://github.com/pallets/flask/blob/master/src/flask/scaffold.py#L150
Voici le code source:
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
J’ai retiré la docstring.
Voici un exemple d’utilisation
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello, World!"
Ainsi grace aux décorateurs, la liaison endpoints - vues est devenue simple, rapide et intuitive.
Nous avons fait le tour d’excellents cas d’utilisation des décorateurs dans le monde réel. Maintenant que c’est fait regardons de plus près comme on les implémente.
Alors un décorateur c’est même d’abord quoi au juste? On dit qu’un décorateur est une fonction prenant en entrée une fonction et retournant une autre fonction. C’est définition est correcte mais elle est limitée de nos jours. Pour être plus précis, un décorateur est une fonction qui prend entrée un callable
et retourne un callable
.
Qu’est ce qu’on callable
? Un callable en Python est un objet qu’on peut appeler comme une fonction c’est à dire faire ceci objet()
notez bien les parenthèses. Les fonctions sont évidement des callables, les classes qui implémentent la méthode spéciale __call__()
le sont aussi et les lambdas. Puisqu’on peut affecter une fonction (pas le résultat de son appel) à une variable alors meme une variable peut être un callable.
Un décorateur est donc censé modifier le comportement de la fonction mais parfois il ne modifie pas le comportement mais peut faire autre chose.
Un décorateur est normalement constitué de deux fonctions. La première apparaissant comme une fonction normale et la deuxième étant à l’interieur de la première. Comme ceci
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
my_decorator
ici correspond actuellement au décorateur lui meme et wrapper
correspond à l’enveloppe. C’est le wrapper (enveloppe) qui est en général retourné par le décorateur.
Lorsque une fonction f est décorée, lorsqu’on l’appelle, c’est en réalité la fonction qui est retournée par le décorateur (en general wrapper) qui sera executée.
Si on comprend cela on a déjà compris l’essence des décorateurs.
Pour tester notre décorateur, écrivons ce code:
@my_decorator
def dire_bonjour():
print("Bonjour!")
## Resultat
>>> dire_bonjour()
Something is happening before the function is called.
Bonjour!
Something is happening after the function is called.
Mais il faut savoir que la notation @decorateur
encore appelée “pie” syntax est juste un raccourci pour la réelle syntaxe. Qui est comme ceci
dire_bonjour = my_decorator(dire_bonjour)
## Resultat
>>> dire_bonjour()
Something is happening before the function is called.
Bonjour!
Something is happening after the function is called.
Ca marche parce qu’un décorateur n’est qu’une fonction après tout.
Maintenant, regardons ceci
import functools
def decorator(func):
@functools.wraps(func)
def wrapper_decorator(*args, **kwargs):
# Modifier le comportement avant l'appel
value = func(*args, **kwargs)
# Modifier le comportement après l'appel
return value
return wrapper_decorator
Voici le template standard, vous pouvez l’utiliser pour écrire votre décorateur. Décorticons les choses:
def decorator(func)
le décorateur lui meme, le paramètre func est la fonction decorée
@functools.wraps(func)
la fonction wraps permet de conserver les attributs de la fonction décorée comme par exemple son nom (func.__name__
)
def wrapper_decorator(*args, **kwargs):
le wrapper. Comme le wrapper doit accepter les mêmes paramètres que la fonction décorée et comme on ne veut pas tout le temps modifier sa signature, on accepte et recupère simplement tous les paramètres (nommés et positionnels) avec *args
et **kwargs
value = func(*args, **kwargs)
on appelle la fonction décorée on passe les paramètres reçus et on sauvegarde le résultat
return value
on retourne le resultat de la fonction décorée
return wrapper_decorator
on retourne le wrapper.
Maintenant voyons un exemple d’application de ce template.
En mathématiques il existe certaines fonctions qui ne travaillent que sur les entiers positifs ou parfois on veut limiter l’utilisation d’une fonction à des valeurs entières strictement positives. On serait tenté de mettre le controle du paramètre à l’interieur des fonctions mais non seulement c’est répetitif et c’est moche. En cas d’erreur dans le code du controle on devra le modifier partout. Il est plus simple d’utiliser un décorateur car la fonction et le controle d’accès à la fonction seront ainsi decouplés.
Sans plus tarder le code:
import functools
from math import sqrt
def strict_entier_positif(func):
@functools.wraps(func)
def wrapper_decorator(n):
if isinstance(n, int) and n > 0:
value = func(n)
return value
else:
raise ValueError("{} n'est pas un entier strictement positif".format(n))
return wrapper_decorator
def racine(n):
return sqrt(n)
def negative(n): # renvoie toujours un nombre negatif
return -1 * n
### Tests
>>> racine(-1)
Retraçage (dernier appel le plus récent) :
Shell Python, prompt 5, line 1
# Used internally for debug sandbox under external interpreter
Shell Python, prompt 4, line 2
if __name__ == '__main__':
builtins.ValueError: math domain error
>>> racine = strict_entier_positif(racine)
>>> racine(-1)
Retraçage (dernier appel le plus récent) :
Shell Python, prompt 7, line 1
# Used internally for debug sandbox under external interpreter
Shell Python, prompt 2, line 8
builtins.ValueError: -1 n'est pas un entier strictement positif
>>> negative(-1)
1 # le resultat n'est pas négatif
>>> negative = strict_entier_positif(negative)
>>> negative(-1)
Retraçage (dernier appel le plus récent) :
Shell Python, prompt 11, line 1
# Used internally for debug sandbox under external interpreter
Shell Python, prompt 2, line 8
builtins.ValueError: -1 n'est pas un entier strictement positif
>>> negative(21)
-21
>>> negative(21.5)
Retraçage (dernier appel le plus récent) :
Shell Python, prompt 13, line 1
# Used internally for debug sandbox under external interpreter
Shell Python, prompt 2, line 8
builtins.ValueError: 21.5 n'est pas un entier strictement positif
On voit bien comment le décorateur a bloqué l’execution de la fonction décorée quand le paramètre n’était pas un entier strictement supérieur à 0.
Nous avons re-découverts les décorateurs. Allant de ce quoi ils peuvent servir à comment les écrire. Avec le template standard on peut déjà facilement écrire soit même ces super décorateurs. Désormais lorsque vous serez en face d’un décorateur vous saurez un peu ce qu’il fait en interne sans avoir besoin de lire son code source.
Mais nous n’avons pas tout fait. Les décorateurs peuvent recevoir aussi des paramètres, ils peuvent même etre chainés, tout ceci nous le verrons dans la 2e partie qui sera consacrée aux décorateurs avancés.
Si vous voulez être automatiquement notifiés à la sortie de la partie 2, suivez-moi (en cliquant sur le bouton follow à cote de mon username). Si vous détectez une erreur dans mes codes ou mes textes n’hesitez pas à me laisser un commentaire. Les questions sont aussi les bienvenues.
Je suis développeur Python de metier, entrepreneur à mes heures perdues et lecteur assidu.
Contact: @jefcolbi
Utilisation de deque à la place de list
Bonne configuration de Postgresql pour un projet Django
Ce que je pense de l'unité et du vivre ensemble au Cameroun, pays aux 234 tribus.
Introduction au cinéma. Présentation des films cultes, des sagas classiques et de bons acteurs que tout bon cinéphile se doit de connaitre.
Dans ce tuto, nous allons voir comment télécharger des fichiers avec le package python requests.
Progression ahurissante de SpaceX
Vous avez déjà crée votre boutique sur Yaknema et vous souhaitez ajouter des produits/articles ? Cet article est fait pour vous....
Un article qui explique comment jouir de youtube premium de manière sécurisée et légale.
Vous avez tout le temps des problèmes de batterie? Voici le secret pour la conserver pendant plus longtemps.
Guide pas à pas sur comment installer le package python mysqlclient sur votre système.
We will see the best way to serve a django or flask app using bjoern via unix socket.
Vous vous êtes toujours demander qu'est ce qui fait la différence entre les bons programmeurs et les moins bons, et comment rejoindre le club des bons...
Vous venez de vous inscrire sur Yaknema et vous ne savez pas comment démarrer? Nous allons vous montrer pas à pas....
Une liste des ennemis du cerveau à éviter à tout prix
Installation, configuration et utilisation de pre-commit pas à pas.
Copyright © 2020 | Powered By | Yaknema SARL