保留装饰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?
任何其他方法?
-
安装装饰模块:
$ pip install decorator
-
调整
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_ints
function – 它现在是固定的。
看看装饰模块 – 特别是装饰装饰,它解决了这个问题。
这是通过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
第二个选项:
- 安装包装模块:
$ 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