La préservation des signatures de fonctions décoré
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 d'un type spécifique, effectuer une journalisation, de mettre en œuvre memoization, 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
Bien le tout jusqu'à présent. Il y a un problème, cependant. Décoré de la fonction n'est pas de conserver 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 de 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 encore un problème: la signature de la fonction est mal. L'information "*args, **kwargs" est à côté de rien.
Quoi faire? Je pense à deux simple mais imparfait solutions de contournement:
1 -- Inclure 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
Ce qui est mauvais en raison de la duplication. La signature ne sera pas affiché correctement dans générée automatiquement de la documentation. Il est facile de mettre à jour la fonction et de l'oublier sur la modification de la docstring, ou de faire une faute de frappe. [Et oui, je suis conscient du fait que la docstring déjà doublons le corps de la fonction. S'il vous plaît ignorer ce; funny_function est juste un hasard.]
2 -- ne Pas utiliser un décorateur, ou utiliser à des fins spéciales de décorateur pour chaque signature:
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 très bien pour un ensemble de fonctions ayant la même signature, mais c'est inutile en général. Comme je l'ai dit au début, je veux être en mesure d'utiliser les décorateurs entièrement de façon générique.
Je suis à la recherche d'une solution qui est entièrement en général, et de l'automatique.
La question est donc: est-il un moyen de modifier la décorées signature de fonction après qu'il a été créé?
Sinon, puis-je écrire un décorateur que des extraits de la signature de la fonction et l'utilise à la place de "*kwargs, **kwargs" lors de la construction de la décorées fonction? Comment puis-je extraire cette information? Comment dois-je construire le décoré de la fonction -- avec exec?
Toutes les autres approches?
- N'a jamais dit "out of date". J'étais plus ou moins demandais ce que
inspect.Signature
ajouté à traiter décorés avec des fonctions.
Vous devez vous connecter pour publier un commentaire.
Installer décorateur module:
Adapter la définition de
args_as_ints()
:Python 3.4+
functools.d'enveloppe()
dans stdlib préserve des signatures depuis Python 3.4:functools.wraps()
est disponible au moins depuis Python 2.5 mais il ne permet pas de conserver la signature il y a:Avis:
*args, **kwargs
au lieu dex, y, z=3
.wrapper.__signature__ = inspect.signature(func)
avant de retournerwrapper
.wrapper.__signature__
aide sur des versions antérieures? (quelles versions avez vous testé?)inspect.signature
n'est pas disponible en Python 2, donc il ne va pas aider. En Python 3.4,functools.wraps()
ne permet pas de définir__signature__
de sorte que la signature ne s'affiche pas correctement dans IPython. Réglage explicitement résout le problème. Si cela ne résout pas le cas des OP de problème, mais il ne résoudra pas le mien.functools.wraps()
est cassé et pas IPython?__signature__
. Par exemple, la dernière version de Ubuntu (1.2.1) exige__signature__
pour être ensemble, alors qu'il travaille dans les sections 3.2.1 à partir de pip. Voir gist.github.com/anonymous/3d97523517abe0f55dc9.help()
produit le résultat correct, la question est de savoir quel logiciel doit être fixe:functools.wraps()
ou IPython? En tout cas, d'attribuer manuellement__signature__
est une solution de contournement, au mieux-ce n'est pas une solution à long terme.inspect.getfullargspec()
n'est toujours pas de retour de la bonne signature pourfunctools.wraps
en python 3.4 et que vous devez utiliserinspect.signature()
à la place.functools.wraps
encore ne permet pas de conserver le comportement (TypeError
en cas de mauvais arguments). Voir le détail de la réponse ici: stackoverflow.com/a/55163816/7262247Ce problème est résolu avec Python standard library
functools
et plus précisémentfunctools.s'enroule
fonction, qui est conçu pour "mise à jour d'une fonction wrapper pour ressembler à la enveloppé fonction". C'est un comportement dépend de la version de Python, cependant, comme indiqué ci-dessous. Appliquée à l'exemple de la question, le code devrait ressembler à:Lorsqu'il est exécuté en Python 3, ce serait de produire les éléments suivants:
Son seul inconvénient est qu'en Python 2 toutefois, il n'a pas de fonction de mise à jour de la liste d'arguments. Lorsqu'il est exécuté en Python 2, il va produire:
Il y a un décorateur module avec
decorator
décorateur, vous pouvez utiliser:Puis la signature et à l'aide de la méthode est préservé:
EDIT: J. F. Sebastian remarquer que je n'ai pas modifier
args_as_ints
fonction -- c'est fixé maintenant.Prendre un coup d'oeil à la décorateur module spécifiquement la décorateur décorateur, ce qui résout ce problème.
Deuxième option:
$ easy_install enveloppé
enveloppé ont un bonus, de préserver la classe de signature.
Comme indiqué ci-dessus dans jfs réponse ; si vous êtes préoccupé par la signature, en termes d'apparence (
help
, etinspect.signature
), puis à l'aidefunctools.wraps
est parfaitement bien.Si vous êtes préoccupé par la signature en termes de comportement (en particulier
TypeError
en cas de contestation, décalage),functools.wraps
ne pas le conserver. Vous devriez plutôt utiliserdecorator
pour cela, ou à mon généralisation de son moteur de base, nommémakefun
.Voir aussi ce post à propos de
functools.s'enroule
.