Je me demandais s'il y avait un moyen pour une fonction décorée de faire référence à un objet créé par le wrapper d'un décorateur. Ma question s'est posée lorsque je pensais utiliser un décorateur pour:
Cependant, la fonction décorée aurait besoin de faire référence à la figure créée par le wrapper. Comment la fonction décorée peut-elle faire référence à cet objet? Faut-il forcément recourir à des variables globales?
Voici un petit exemple où je référence dans la fonction décorée une variable créée dans le wrapper (mais je n'ai pas réussi à le faire sans peaufiner avec des globals):
def my_decorator(func): def my_decorator_wrapper(*args, **kwargs): global x x = 0 print("x in wrapper:", x) return func(*args, **kwargs) return my_decorator_wrapper @my_decorator def decorated_func(): global x x += 1 print("x in decorated_func:", x) decorated_func() # prints: # x in wrapper: 0 # x in decorated_func: 1
Je sais que cela se ferait facilement en classe, mais je pose cette question par curiosité.
3 Réponses :
Oui, la fonction peut s'y référer en se regardant.
si cela semble un peu compliqué, c'est parce que les décorateurs qui prennent des paramètres ont besoin de cette structure particulière pour fonctionner. voir Décorateurs avec paramètres?
@declare_view( viewmanager_cls=backend.VueManagerDetailPSCLASSDEFN, template_name="pssecurity/detail.html", objecttype=constants.OBJECTTYPE_PERMISSION_LIST[0], bundle_name="pssecurity/detail.psclassdefn", ) def psclassdefn_detail(request, CLASSID, dbr=None, PORTAL_NAME="EMPLOYEE"): """ """ f_view = psclassdefn_detail viewmanager = f_view.viewmanager_cls(request, mdb, f_view=f_view) ...do things based on the parameters... return viewmanager.HttpResponse(f_view.template_name)
x in decorated_func: 2
@declare_view( x=2 ) def decorated_func(): #the function can look at its own name, because the function exists #by the time it gets called. print("x in decorated_func:", decorated_func.x) decorated_func()
En pratique, je l'ai beaucoup utilisé. L'idée pour moi est d'associer les fonctions de vue Django à des classes de données et à des modèles de backend particuliers avec lesquels elles doivent collaborer. Parce qu'il est déclaratif, je peux introspecter toutes les vues Django et suivre leurs URL associées ainsi que des objets de données et des modèles personnalisés. Fonctionne très bien, mais oui, la fonction s'attend à ce que certains attributs existent sur elle-même. Il ne sait pas qu'un décorateur les a définis.
Oh, et il n'y a aucune bonne raison, dans mon cas, pour que ceux-ci soient passés en tant que paramètres dans mes cas d'utilisation, ces variables contiennent essentiellement des valeurs codées en dur qui ne change, à partir du point de vue de la fonction.
Bizarre au début, mais assez puissant et aucun inconvénient d'exécution ou de maintenance.
Voici un exemple en direct qui met cela en contexte. >
def declare_view(**kwds): """declaratively assocatiate a Django View function with resources """ def actual_decorator(func): for k, v in kwds.items(): setattr(func, k, v) return func return actual_decorator
vous pouvez définir des attributs sur les fonctions !? 😨
En Python, tout est un objet. Donc, oui, puisque les vues sont des objets Python et n'ont pas de __slots__
, vous pouvez y définir des attributs arbitraires. Entre Python 2 et 3, leurs propres attributs internes ont un peu changé. Par exemple, le nom peut être func .__ name__
en 3, mais quelque chose d'un peu plus compliqué en 2.
edit: lisez "fonctions" au lieu de "vues" dans mon commentaire précédent.
Essayez de éviter d'utiliser des variables globales .
Il existe un moyen canonique de passer une valeur à une fonction: arguments.
Passez l'objet en tant qu'argument à la fonction décorée lorsque le wrapper est appelé.
from functools import wraps def decorator(f, obj={}): @wraps(f) def wrapper(*args): return f(obj, *args) return wrapper @decorator def func(params) params['foo'] = True @decorator def gunc(params) print(params) func() # proof that gunc receives the same object gunc() # prints {'foo': True}
Si vous devez passer le même objet à toutes les fonctions, stockez-le comme argument par défaut de votre décorateur est une alternative.
from functools import wraps def decorator(f): obj = 1 @wraps(f) def wrapper(*args): return f(obj, *args) return wrapper @decorator def func(x) print(x) func() # prints 1
Ce qui précède crée un dict
privé commun auquel seuls les fonctions décorées peuvent accéder. Puisqu'un dict
est modifiable, les changements seront reflétés dans les appels de fonction.
Cet article désigne les classes en tant que décorateurs, ce qui semble une manière plus élégante de désigner l'état défini dans le décorateur. Il repose sur des attributs de fonction et utilise la méthode spéciale .__ call __ ()
dans la classe de décoration.
Voici mon exemple revisité en utilisant une classe au lieu d'une fonction en tant que décorateur:
class my_class_decorator: def __init__(self, func): self.func = func self.x = 0 def __call__(self, *args, **kwargs): print("x in wrapper:", self.x) return self.func(*args, **kwargs) @my_class_decorator def decorated_func(): decorated_func.x += 1 print("x in decorated_func:", decorated_func.x) decorated_func() # prints: # x in wrapper: 0 # x in decorated_func: 1
Le décorateur peut fournir un paramètre supplémentaire à la fonction encapsulée lors de son appel. Normalement, ce serait une mauvaise idée, car la décoration est quelque chose que vous pouvez éventuellement faire à une fonction, mais vous parlez d'un scénario avec un couplage beaucoup plus étroit entre décoré et décorateur que d'habitude.
Dans Python 3, vous pouvez utiliser
nonlocal
pour faire référence à des objets connus pour être créés par le décorateur.@chepner: Non,
nonlocal
ne fonctionne pas comme ça.nonlocal
est pour les variables de fermeture.@ user2357112 OK, à droite. J'étais confus dans les fermetures et la portée dynamique.
Pouvez-vous ajouter un extrait de code pour rendre votre demande plus claire?
Il existe différentes manières d'y parvenir, mais aucune ne semble particulièrement bonne, et vraiment, si votre fonction décorée a besoin de comprendre quelque chose sur les éléments internes du décorateur, ce n'est probablement pas un bon cas d'utilisation pour un décorateur.
d'accord avec @ juanpa.arrivillaga, les fonctions décorées ne doivent pas être conscientes de leur décorateur. Cela va à l'encontre du but