Skip to content

Les décorateurs

Notion de Closure

Généralités

Une fonction retournée par une fonction retient le contexte dans lequel elle a été créée :

def outer(param):
    value = param
    def inner(modif):
        return value * modif

Va s'utiliser :

>>> my_answer = outer(42)
>>> my_answer(10)
420

Modifier l'environnement d'exécution d'une closure

Comme pour toute variable, les variables définis dans la fonction externe ne sont visibles qu'en lecture seule pour la fonction interne.

def outer(name):
    count = 0
    def inner():
        print(f"inner func for {name}")
        nonlocal count
        count += 1

    return inner

Accéder aux variables de l'environnement d'exécution

Les variables définies par la fonction externe ne sont pas accessibles dans l'environnement qui a récupéré la fonction interne. L'astuce consiste à définir une fonction accesseur attachée à la fonction retournée.

def outer(name):
    count = 0

    def inner():
        print(f"inner func for {name}")
        nonlocal count
        count += 1

    def inner_get_count():
        return count

    inner.get_count = inner_get_count

    return inner

Nous pouvons alors utiliser :

>>> click = outer()
>>> click()
>>> click()
>>> click.get_count()
2

Vers les décorateurs

Structure générale des décorateurs

def sandwich(content):
    def wrapper():
        print("/TTTTTTTTTT\\")
        content()
        print("\\__________/")

    return wrapper

Ce qui peut s'appeler de la manière suivante :

def parisien():
    print("Jambon")
    print("Beurre")

sandwich(parisien)()

Mais aussi

@sandwich
def parisien():
    print("Jambon")
    print("Beurre")

Passage d'arguments

L'usage de variadics permet une cohésion faible entre le décorateur et la fonction à décorer.

def sandwich(content):
    def wrapper(*args, **kargs):
        print("/TTTTTTTTTT\\")
        content(*args, **kargs)
        print("\\__________/")

    return wrapper

Valeur en retour

def sandwich(content):
    def wrapper(*args, **kargs):
        print("/TTTTTTTTTT\\")
        value = content(*args, **kargs)
        print("\\__________/")

        return value

    return wrapper

Décorateur paramétré

def name(key1=value1, key2=value2, ...):
    def decorator_name(func):
        ...  # La fonction wrapper

Mais ceci fait que le décorateur ne peut être utilisé sans parenthèses.

Conserver la syntaxe simple avec le décorateur paramétré

def name(_func=None, *, key1=value1, key2=value2, ...):
    def decorator_name(func):
        ...  # La fonction wrapper

    if _func is None:
        return decorator_name
    else:
        return decorator_name(_func)

Bonus : remplacer les métadonnées du wrapper

Dans le cas de l'utilisation d'un wrapper, l'introspection de la fonction sera faussé. Nous n'avons plus la fonction d'origine mais la fonction wrapper. Faites le test sur la base du décorateur sandwich, vous devriez avoir quelque chose de similaire à :

>>> sandwich.__name__
'wrapper'

Pour préserver les informations de la fonction d'origine :

import functools

def sandwich(content):
    @functools.wraps(content)
    def wrapper():
        print("/TTTTTTTTTT\\")
        content()
        print("\\__________/")

    return wrapper