Je regarde une fonction dans un backend de flacon avec un décorateur et je pense à l'importer dans un autre script et à le décorer d'une manière différente. Quelqu'un sait-il ce qui se passe lorsque vous l'importez, que le décorateur l'accompagne ou non?

J'ai jeté un œil à ceci mais ça discute plus ce qui se passe dans le même script.

1
cardamom 12 mars 2019 à 17:45

2 réponses

Meilleure réponse

Décorer une fonction

@some_decorator
def some_func(...):
    ...

Équivaut à appliquer une fonction à un autre objet:

def some_func(...):
    ...

some_func = some_decorator(some_func)

Lorsque vous importez le module, tout ce à quoi vous avez accès est l'objet actuellement lié à some_func, qui est la valeur de retour de some_decorator appliquée à la fonction d'origine. Sauf si la chose retournée some_decorator comprend une référence à la fonction d'origine non décorée, vous n'y avez pas accès depuis le module importé.

Un exemple d'exposition de l'original:

def some_decorator(f):
    def _(*args, *kwargs):
        # Do some extra stuff, then call the original function
        # ...
        return f(*args, **kwargs)
    _.original = f
    return _

@some_decorator
def some_func(...):
    ...

Lorsque vous importez le module, some_module.some_func fait référence à la fonction décorée, mais la fonction non décorée d'origine est disponible via some_module.some_func.original, mais uniquement car le décorateur a été écrit pour le rendre disponible. (Comme le souligne Martijn Peters, le wraps décorateur fait cela - et quelques autres belles choses - pour vous, mais le décorateur doit toujours utiliser wraps.)

2
chepner 12 mars 2019 à 14:51

Non, l'importation d'une fonction décorée ne supprimera pas le décorateur.

L'importation récupère l'objet actuel de l'espace de noms global du module source et la décoration d'une fonction entraîne le stockage de la valeur de retour du décorateur dans l'espace de noms global.

L'importation d'un module est principalement du sucre syntaxique pour modulename = sys.modules['modulename'] (pour import modulename) et objectname = sys.modules['modulename'].objectname affectations (pour from modulename import objectname, dans les deux cas après avoir vérifié que sys.modules a le module souhaité chargé), et les globaux dans un module sont la même chose que les attributs sur un objet de module. La décoration n'est que du sucre syntaxique pour functionname = decorator(functionobject).

Si vous devez ajouter un nouveau décorateur à la fonction importée, appelez simplement le décorateur:

from somemodule import somedecoratedfunction

newname_or_originalname = decorator(somedecoratedfunction)

Si la fonction décorée importée ne se prête pas à être décorée à nouveau dans un nouveau calque, ou si vous souhaitez accéder à la fonction non décorée d'origine, voyez si l'objet a un attribut __wrapped__:

from somemodule import somedecoratedfunction

unwrapped_function = somedecoratedfunction.__wrapped__

Un décorateur bien écrit utilise le @functools.wraps() décorateur, qui définit cet attribut pour qu'il pointe vers l'original :

>>> from functools import wraps
>>> def demodecorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwargs):
...         print("Decorated!")
...         return f(*args, **kwargs)
...     return wrapper
...
>>> @demodecorator
... def foo(name):
...     print(f"Hello, {name or 'World'}!")
...
>>> foo('cardamom')
Decorated!
Hello, cardamom!
>>> foo.__wrapped__('cardamom')
Hello, cardamom!
3
Martijn Pieters 12 mars 2019 à 14:54