保留装饰function的签名

假设我写了一个装饰器来做一些非常通用的事情。 例如,它可能会将所有参数转换为特定types,执行日志logging,实现记忆等。

这里是一个例子:

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 

一切都很好,迄今。 但是有一个问题。 装饰的function不保留原始function的文档:

 >>> help(funny_function) Help on function g in module __main__: g(*args, **kwargs) 

幸运的是,有一个解决方法:

 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 

这一次,函数名称和文档是正确的:

 >>> help(funny_function) Help on function funny_function in module __main__: funny_function(*args, **kwargs) Computes x*y + 2*z 

但是还有一个问题:function签名是错误的。 信息“* args,** kwargs”是无用的。

该怎么办? 我可以想到两个简单但有缺陷的解决方法:

1 – 在文档string中包含正确的签名:

 def funny_function(x, y, z=3): """funny_function(x, y, z=3) -- computes x*y + 2*z""" return x*y + 2*z 

这很糟糕,因为重复。 自动生成的文档中签名仍然不能正确显示。 更新函数并忘记更改文档string或打字错误很容易。 [ 是的,我意识到文档string已经复制了函数体。 请忽略这个; funny_function只是一个随机的例子。 ]

2 – 不使用装饰器,或对每个特定的签名使用特殊用途的装饰器:

 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 

这对于一组具有相同签名的函数来说工作得很好,但一般来说是没用的。 正如我一开始所说的,我希望能够完全一般地使用装饰器。

我正在寻找一个完全一般的解决scheme,并且是自动的。

所以问题是:是否有办法编辑装饰后的function签名创build后?

否则,我可以写一个装饰器,提取函数签名,并使用该信息,而不是“* kwargs,** kwargs”构造装饰函数时? 我如何提取这些信息? 我应该如何构build装饰函数 – 与exec?

任何其他方法?

  1. 安装装饰模块:

     $ pip install decorator 
  2. 调整args_as_ints()定义:

     import decorator @decorator.decorator def args_as_ints(f, *args, **kwargs): args = [int(x) for x in args] kwargs = dict((k, int(v)) for k, v in kwargs.items()) return f(*args, **kwargs) @args_as_ints def funny_function(x, y, z=3): """Computes x*y + 2*z""" return x*y + 2*z print funny_function("3", 4.0, z="5") # 22 help(funny_function) # Help on function funny_function in module __main__: # # funny_function(x, y, z=3) # Computes x*y + 2*z 

Python 3.4+

自从Python 3.4以来,stdlib中的functools.wraps()保留了签名:

 import functools def args_as_ints(func): @functools.wraps(func) def wrapper(*args, **kwargs): args = [int(x) for x in args] kwargs = dict((k, int(v)) for k, v in kwargs.items()) return func(*args, **kwargs) return wrapper @args_as_ints def funny_function(x, y, z=3): """Computes x*y + 2*z""" return x*y + 2*z print(funny_function("3", 4.0, z="5")) # 22 help(funny_function) # Help on function funny_function in module __main__: # # funny_function(x, y, z=3) # Computes x*y + 2*z 

至less从Python 2.5开始, functools.wraps()是可用的,但它并不保留签名:

 help(funny_function) # Help on function funny_function in module __main__: # # funny_function(*args, **kwargs) # Computes x*y + 2*z 

注意: *args, **kwargs而不是x, y, z=3

有一个装饰器模块与decorator器装饰你可以使用:

 @decorator def args_as_ints(f, *args, **kwargs): args = [int(x) for x in args] kwargs = dict((k, int(v)) for k, v in kwargs.items()) return f(*args, **kwargs) 

然后保存该方法的签名和帮助:

 >>> help(funny_function) Help on function funny_function in module __main__: funny_function(x, y, z=3) Computes x*y + 2*z 

编辑:JF塞巴斯蒂安指出,我没有修改args_as_intsfunction – 它现在是固定的。

看看装饰模块 – 特别是装饰装饰,它解决了这个问题。

这是通过Python的标准库函数functools来解决的,具体来说就是functools.wraps函数,它被devise用来“ 更新包装函数看起来像包装函数 ”。 它的行为取决于Python版本,但是,如下所示。 应用到问题的例子中,代码如下所示:

 from functools import wraps def args_as_ints(f): @wraps(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 

在Python 3中执行时,会产生以下结果:

 >>> funny_function("3", 4.0, z="5") 22 >>> help(funny_function) Help on function funny_function in module __main__: funny_function(x, y, z=3) Computes x*y + 2*z 

它唯一的缺点是在Python 2中,它不更新函数的参数列表。 当在Python 2中执行时,它将产生:

 >>> help(funny_function) Help on function funny_function in module __main__: funny_function(*args, **kwargs) Computes x*y + 2*z 

第二个选项:

  1. 安装包装模块:

$ easy_install包装

包装有奖金,保留class级签名。

 import wrapt import inspect @wrapt.decorator def args_as_ints(wrapped, instance, args, kwargs): if instance is None: if inspect.isclass(wrapped): # Decorator was applied to a class. return wrapped(*args, **kwargs) else: # Decorator was applied to a function or staticmethod. return wrapped(*args, **kwargs) else: if inspect.isclass(instance): # Decorator was applied to a classmethod. return wrapped(*args, **kwargs) else: # Decorator was applied to an instancemethod. return wrapped(*args, **kwargs) @args_as_ints def funny_function(x, y, z=3): """Computes x*y + 2*z""" return x * y + 2 * z >>> funny_function(3, 4, z=5)) # 22 >>> help(funny_function) Help on function funny_function in module __main__: funny_function(x, y, z=3) Computes x*y + 2*z 

import wrapt import inspect @wrapt.decorator def args_as_ints(wrapped, instance, args, kwargs): if instance is None: if inspect.isclass(wrapped): # Decorator was applied to a class. return wrapped(*args, **kwargs) else: # Decorator was applied to a function or staticmethod. return wrapped(*args, **kwargs) else: if inspect.isclass(instance): # Decorator was applied to a classmethod. return wrapped(*args, **kwargs) else: # Decorator was applied to an instancemethod. return wrapped(*args, **kwargs) @args_as_ints def funny_function(x, y, z=3): """Computes x*y + 2*z""" return x * y + 2 * z >>> funny_function(3, 4, z=5)) # 22 >>> help(funny_function) Help on function funny_function in module __main__: funny_function(x, y, z=3) Computes x*y + 2*z