Pense-bête python : les décorateurs (decorators)

Je n'ai pas souvent la possibilité de développer en python en dehors de scripts ponctuels, et donc souvent j'oublie certaines fonctionnalités utiles et je passe du temps pour retrouver comment cela marche. L'objet de cette série de billets est donc de me faire un pense-bête de fonctionnalités du langage python pour que je ne perde plus de temps à rechercher et relire les informations dispersées sur la toile. Et si cela peut vous être utile : tant mieux !

Certe c'est un pense-bête qui m'est essentiellement destiné, mais puisque je le publie, autant essayer d'être compréhensible :)Les décorateurs sont des "fioritures" syntaxique qui permettent “d'encadrer” une fonction afin de faire s'exécuter du code avant ou après la fonction. On pourrait tout à fait faire la même chose sans ces ajouts au langage, mais cela serait un peu plus lourd à lire et écrire.

Un premier exemple :

def monDécorateur(fonction_décorée):
    def nom_de_fonction_bidon(argument_de_la_fonction_décorée):
        print ("==========================")
        fonction_décorée(argument_de_la_fonction_décorée)
        print ("==========================")
    return nom_de_fonction_bidon


@monDécorateur
def newprint(texte):
    print (texte)


newprint("coucou")

Résultat :

>>> newprint("coucou")
==========================
coucou
==========================

Ce qu'il faut comprendre dans la définition de monDecorateur c'est que son argument fonction_décorée prendra la valeur de la fonction décorée : ici newprint, et cette fonction sera donc executée entre les deux print().Alors là comme ça l'intérêt ne saute pas forcément aux yeux, et surtout ce décorateur souffre d'une limitation : il ne peut décorer que des fonctions ayant un unique argument. Nous allons déjà remédier à ce problème pour en faire un décorateur un peu plus universel:

def monDécorateur(fonction_décorée):
    def nom_de_fonction_bidon(*args, **kwargs):
        print ("==========================")
        fonction_décorée(*args, **kwargs)
        print ("==========================")
    return nom_de_fonction_bidon


@monDécorateur
def newprint(texte):
    print (texte)

Le résultat sera le même que précédemment, mais cette fois-ci le décorateur fonctionnera quelque-soit le nombre d'arguments de la fonction décorée : je ne l'ai pas précisé avant il faut savoir qu'un même décorateur peut être utilisé et réutilisé pour plusieurs fonctions. Note : les noms args et kargs n'ont aucune importance, ce sont les étoiles qui permettent de récupérer les arguments classique et nommés.

Bon ben ça sert à quoi ?

Ok ok ok, voici quelques exemples un peu plus utiles, le premier un débugger maison :

def monDébogueur (fonction_décorée) :
    def ploufplouf (*args, **kwargs) :
        print("[Début] " + fonction_décorée.__name__)
        chaine = "Arguments : "
        for element in args :
            chaine += str(element) + " "
        print (chaine)
        chaine = "Arguments nommés : "
        for nom, valeur in kwargs.items():
            chaine += '{0} = {1} , '.format(nom, valeur)
        print (chaine)
        fonction_décorée(*args, **kwargs)
        print("[Fin] " + fonction_décorée.__name__)
    return ploufplouf

@monDébogueur
def newprint(texte):
    print (texte)

@monDébogueur
def monAddition(a,b):
    return (a+b)

Résultat :

>>> monAddition(3,4)
[Début] monAddition
Arguments : 3 4 
Arguments nommés : 
[Fin] monAddition

Et là tout de suite, ça a un peu plus d'intérêt.Aller un deuxième exemple, avec un ralentisseur d'exécution :

import time

def monRalentisseur (fonction_décorée) :
    def ploufplouf (*args, **kwargs) :
        fonction_décorée(*args, **kwargs)
        time.sleep(0.5)
    return ploufplouf

@monRalentisseur
def newprint(texte):
    print (texte)

Résultat :

>>>for i in range(5) : newprint("hello") 
...
hello
hello
hello
hello
hello

Bon ok, textuellement le ralentissement n'est pas évident ici, mais en vrai dans un terminal, ça marche ;)

Notez que l'on peut cumuler les décorateurs :

@monRalentisseur
@monDébogueur
def newprint(texte):
    print (texte)

Sources (en anglais)

Un tuto simple ici : http://www.programiz.com/python-programming/decorator

Un très bon tuto avec des cas pratiques ici : http://www.python-course.eu/python3_decorators.php

Une discussion très instructive ici : http://stackoverflow.com/questions/20945366/python-decorators

Comments !

blogroll

social