假设我编写了一个装饰器来做一些非常通用的东西 . 例如,它可能会将所有参数转换为特定类型,执行日志记录,实现memoization等 .
这是一个例子:
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
到目前为止一切都很好 . 然而,有一个问题 . 装饰函数不保留原始函数的文档:
>>> 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
但仍存在一个问题:功能签名是错误的 . 信息“* args,** kwargs”几乎没用 .
该怎么办?我可以想到两个简单但有缺陷的解决方法:
1 - 在docstring中包含正确的签名:
def funny_function(x, y, z=3):
"""funny_function(x, y, z=3) -- computes x*y + 2*z"""
return x*y + 2*z
这很糟糕,因为重复 . 签名仍无法在自动生成的文档中正确显示 . 更新函数很容易,忘记更改文档字符串或打字错误 . [是的,我知道docstring已经复制了函数体这一事实 . 请忽略这一点; 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
这适用于具有相同签名的一组函数,但一般来说它没用 . 正如我在开始时所说,我希望能够完全使用装饰器 .
我正在寻找一种完全通用且自动化的解决方案 .
所以问题是:有没有办法在创建装饰函数签名后对其进行编辑?
否则,我可以编写一个提取器来提取函数签名并在构造装饰函数时使用该信息而不是“* kwargs,** kwargs”吗?如何提取该信息?我应该如何构建装饰函数 - 使用exec?
还有其他方法吗?
6 回答
args_as_ints()
的定义:Python 3.4
functools.wraps() from stdlib自Python 3.4起保留签名:
functools.wraps()
可用at least since Python 2.5但它不保留那里的签名:注意:
*args, **kwargs
而不是x, y, z=3
.这是通过Python的标准库
functools
,特别是functools.wraps函数解决的,该函数旨在“将包装函数更新为包装函数” . 但是,它的行为取决于Python版本,如下所示 . 应用于问题的示例,代码如下所示:在Python 3中执行时,会产生以下结果:
它唯一的缺点是在Python 2中,它不会更新函数的参数列表 . 在Python 2中执行时,它将产生:
你可以使用decorator module和
decorator
装饰器:然后保留方法的签名和帮助:
编辑:J . F. Sebastian指出我没有修改
args_as_ints
功能 - 它现在已修复 .看一下decorator模块 - 特别是decorator装饰器,它解决了这个问题 .
第二种选择:
$ easy_install包装
保护有奖金,保留类签名 .
如上所述jfs's answer;如果您在外观方面关注签名(
help
和inspect.signature
),那么使用functools.wraps
就完全没问题了 .如果您在行为方面关注签名(特别是在参数不匹配的情况下
TypeError
),functools.wraps
不会保留它 . 您应该使用decorator
,或者我的核心引擎的概括,名为makefun .另见this post about functools.wraps .