Supposons que j'ai écrit un décorateur qui fait quelque chose de très générique. Par exemple, il peut convertir tous les arguments en un type spécifique, effectuer la journalisation, implémenter la mémorisation, etc.
Voici un exemple:
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
>>> funny_function("3", 4.0, z="5")
22
Tout va bien jusqu'à présent. Il y a cependant un problème. La fonction décorée ne conserve pas la documentation de la fonction d'origine:
>>> help(funny_function)
Help on function g in module __main__:
g(*args, **kwargs)
Heureusement, il existe une solution de contournement:
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
g.__name__ = f.__name__
g.__doc__ = f.__doc__
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
Cette fois, le nom de la fonction et la documentation sont corrects:
>>> help(funny_function)
Help on function funny_function in module __main__:
funny_function(*args, **kwargs)
Computes x*y + 2*z
Mais il y a toujours un problème: la signature de la fonction est erronée. L'information "* args, ** kwargs" est presque inutile.
Que faire? Je peux penser à deux solutions de contournement simples mais imparfaites:
1 - Incluez la signature correcte dans la docstring:
def funny_function(x, y, z=3):
"""funny_function(x, y, z=3) -- computes x*y + 2*z"""
return x*y + 2*z
C'est mauvais à cause de la duplication. La signature ne sera toujours pas affichée correctement dans la documentation générée automatiquement. Il est facile de mettre à jour la fonction et d'oublier de changer la docstring ou de faire une faute de frappe. [ Et oui, je suis conscient du fait que la docstring duplique déjà le corps de la fonction. Veuillez ignorer ceci; funny_function est juste un exemple aléatoire. ]
2 - Ne pas utiliser de décorateur, ni utiliser de décorateur spécial pour chaque signature spécifique:
def funny_functions_decorator(f):
def g(x, y, z=3):
return f(int(x), int(y), z=int(z))
g.__name__ = f.__name__
g.__doc__ = f.__doc__
return g
Cela fonctionne bien pour un ensemble de fonctions qui ont une signature identique, mais c'est inutile en général. Comme je l'ai dit au début, je veux pouvoir utiliser les décorateurs de manière entièrement générique.
Je recherche une solution tout à fait générale et automatique.
La question est donc la suivante: existe-t-il un moyen d'éditer la signature de la fonction décorée après sa création?
Sinon, puis-je écrire un décorateur qui extrait la signature de la fonction et utilise cette information au lieu de "* kwargs, ** kwargs" lors de la construction de la fonction décorée? Comment extraire ces informations? Comment dois-je construire la fonction décorée - avec exec?
D'autres approches?
inspect.Signature
ajoutait à la gestion des fonctions décorées.Réponses:
Installez le module décorateur :
Adapter la définition de
args_as_ints()
:Python 3.4+
functools.wraps()
from stdlib préserve les signatures depuis Python 3.4:functools.wraps()
est disponible au moins depuis Python 2.5 mais il n'y conserve pas la signature:Remarque:
*args, **kwargs
au lieu dex, y, z=3
.la source
functools.wraps()
préserve déjà les signatures dans Python 3.4+ (comme indiqué dans la réponse). Voulez-vous dire que la configurationwrapper.__signature__
aide sur les versions antérieures? (quelles versions avez-vous testées?)help()
affiche la signature correcte sur Python 3.4. Pourquoi pensez-vous qu'ilfunctools.wraps()
est cassé et non IPython?help()
produit le résultat correct, la question est de savoir quel logiciel doit être corrigé:functools.wraps()
ou IPython? Dans tous les cas, l'attribution manuelle__signature__
est au mieux une solution de contournement - ce n'est pas une solution à long terme.inspect.getfullargspec()
ne renvoie toujours pas la signature correcte pourfunctools.wraps
python 3.4 et que vous devez utiliser à lainspect.signature()
place.Ceci est résolu avec la bibliothèque standard de Python
functools
et en particulier lafunctools.wraps
fonction, qui est conçue pour « mettre à jour une fonction wrapper pour qu'elle ressemble à la fonction enveloppée ». Son comportement dépend de la version de Python, cependant, comme indiqué ci-dessous. Appliqué à l'exemple de la question, le code ressemblerait à ceci:Une fois exécuté dans Python 3, cela produirait ce qui suit:
Son seul inconvénient est que dans Python 2 cependant, il ne met pas à jour la liste des arguments de la fonction. Lorsqu'il est exécuté en Python 2, il produira:
la source
Il existe un module
decorator
décorateur avec décorateur que vous pouvez utiliser:Ensuite, la signature et l'aide de la méthode sont conservées:
EDIT: JF Sebastian a souligné que je n'ai pas modifié la
args_as_ints
fonction - elle est corrigée maintenant.la source
Jetez un œil au module décorateur - en particulier le décorateur décorateur, qui résout ce problème.
la source
Deuxième option:
$ easy_install wrapt
wrapt ont un bonus, préservez la signature de la classe
la source
Comme commenté ci-dessus dans la réponse de jfs ; si vous êtes préoccupé par la signature en termes d'apparence (
help
, etinspect.signature
), utilisezfunctools.wraps
est parfaitement bien.Si vous êtes préoccupé par la signature en termes de comportement (en particulier
TypeError
en cas de discordance d'arguments),functools.wraps
ne la préserve pas. Vous devriez plutôt utiliserdecorator
pour cela, ou ma généralisation de son moteur de base, nommémakefun
.Voir aussi cet article sur
functools.wraps
.la source
inspect.getfullargspec
n'est pas conservé en appelantfunctools.wraps
.