创建一个结合两个函数而不指定原始函数的调用签名的装饰器
Posted
技术标签:
【中文标题】创建一个结合两个函数而不指定原始函数的调用签名的装饰器【英文标题】:creating a decorator that combines two functions without specifying the calling signature of the original function 【发布时间】:2017-06-18 09:17:43 【问题描述】:我想创建一个装饰器,它结合了两个函数并结合了它们签名中的参数。
我想要的界面:
def f(a, b, c, d, e, f, g, h, i, j, k, l, m, n):
# I am using many parameters to explain the need of not
# needing to type the arguments again.
return a * b * c * d * e * f * g * h * i * j * k * l * m * n
@combines(f)
def g(o, p, *args, **kwargs):
return (o + p) * f(*args, **kwargs)
这基本上应该导致:
def g(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p):
return (o + p) * (a * b * c * d * e * f * g
* h * i * j * k * l * m * n)
我想要这个的原因是因为我真的不知道函数f
的参数(我知道它们,但我不想再次输入它们以使其通用。)
我不确定我是否必须用*args
和**kwargs
打电话给g
,但我认为这是必要的。
这是我走了多远:
import functools
import inspect
def combines(old_func):
old_sig = inspect.signature(old_func)
old_parameters = old_sig.parameters
def insert_in_signature(new_func):
new_parameters = inspect.signature(new_func).parameters
for new_parameter in new_parameters:
if new_parameter in old_parameters.keys():
raise TypeError('`` argument already defined'.format(new_parameter))
@functools.wraps(new_func)
def wrapper(*args, **kwargs):
return old_func(*args, **kwargs) * new_func(*args, **kwargs)
parms = list(old_parameters.values())
for arg, par in new_parameters.items():
if par.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
parms.append(inspect.Parameter(arg, par.kind))
wrapper.__signature__ = old_sig.replace(parameters=parms)
return wrapper
return insert_in_signature
def f(a, b, c, d, e, f, g, h, i, j, k, l, m, n):
return a * b * c * d * e * f * g * h * i * j * k * l * m * n
@combines(f)
def g(o, p, *args, **kwargs):
return (o + p) * f(*args, **kwargs)
这会产生所需的调用签名g
,但它不起作用。
编辑,因为询问了错误消息
例如:g(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-23-2775f64e1b3e> in <module>()
----> 1 g(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)
<ipython-input-18-3a843320e4e3> in wrapper(*args, **kwargs)
13 @functools.wraps(new_func)
14 def wrapper(*args, **kwargs):
---> 15 return old_func(*args, **kwargs) * new_func(*args, **kwargs)
16
17 parms = list(old_parameters.values())
TypeError: f() takes 14 positional arguments but 16 were given
如果我随后按照错误消息并使用g(1,1,1,1,1,1,1,1,1,1,1,1,1,1)
给出 14 个参数:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-24-052802b037a4> in <module>()
----> 1 g(1,1,1,1,1,1,1,1,1,1,1,1,1,1)
<ipython-input-18-3a843320e4e3> in wrapper(*args, **kwargs)
13 @functools.wraps(new_func)
14 def wrapper(*args, **kwargs):
---> 15 return old_func(*args, **kwargs) * new_func(*args, **kwargs)
16
17 parms = list(old_parameters.values())
<ipython-input-18-3a843320e4e3> in g(o, p, *args, **kwargs)
29 @combines(f)
30 def g(o, p, *args, **kwargs):
---> 31 return (o + p) * f(*args, **kwargs)
TypeError: f() missing 2 required positional arguments: 'm' and 'n'
很明显我的实现并没有真正起作用。
【问题讨论】:
具体在什么情况下不起作用?你有其他人可以尝试的完整示例吗?展示下。有错误吗?显示那个。 感谢您的评论,我已经添加了错误信息。 【参考方案1】:您的问题是您使用不同的参数调用 f
函数两次,一次在原始 g
中仅使用其预期参数,一次在包装器中使用所有参数。
你必须选择一个,我的建议是从原来的g
中删除它的调用
我稍微修改了你的代码,但至少我的版本可以在 Python 3.5 中运行:
包装函数的签名列出了f
和g
的所有参数
包装函数接受位置和关键字参数
包装函数在接收到错误数量的参数时引发错误
代码如下:
def combine(ext):
ext_params = inspect.signature(ext).parameters
def wrapper(inn):
inn_params = inspect.signature(inn).parameters
for k in inn_params.keys():
if k in ext_params.keys():
raise TypeError('`` argument already defined'.format(
k))
all_params = list(ext_params.values()) + \
list(inn_params.values())
# computes the signature for the wrapped function
sig = inspect.signature(inn).replace(parameters = all_params)
def wrapped(*args, **kwargs):
# signature bind magically processes positional and keyword arguments
act_args = sig.bind(*args, **kwargs).args
ext_args = act_args[:len(ext_params.keys())] # for external function
inn_args = act_args[len(ext_params.keys()):] # for inner function
return ext(*ext_args) * inn(*inn_args)
w = functools.update_wrapper(wrapped, inn) # configure the wrapper function
w.__signature__ = sig # and set its signature
return w
return wrapper
我现在可以写了:
>>> @combine(f)
def g(o,p):
return o+p
>>> help(g)
Help on function g in module __main__:
g(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)
>>> g(1,1,1,1,1,1,1,1,1,1,1,1,1,p=1, o=1, n=1)
2
【讨论】:
【参考方案2】:我想我明白你想要什么。为此,首先,我建议使用 OOP,以使事情更清晰。我认为您也不需要所有代码来处理参数或inspect
模块。我认为你应该保持简单:
我认为部分问题在于您使用相同的参数调用两个函数,并且它们被认为参数太多或太少,在这种情况下您需要修改g
。我想没有办法弄清楚哪些参数去哪个函数所以这里有一些解决方案:
这是一种解决方案:
def combines(func):
class Decorator:
def __init__(self, f):
self.outer_func = func
self.inner_func = f
def __call__(self, *args, **kwargs):
res, a, p = self.inner_func(*args, **kwargs)
return res * self.outer_func(*a, **p)
return Decorator
def f(a, b, c, d, e, f, g, h, i, j, k, l, m, n):
return a * b * c * d * e * f * g * h * i * j * k * l * m * n
@combines(f)
def g(o, p, *args, **kwargs):
return o+p, args, kwargs
print g(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1) # output: 2
这是另一种解决方案,它使用第三个函数将两者结合起来:
def combines(func1, func2):
class Decorator:
def __init__(self, f):
self.outer_func = func1
self.inner_func = func2
self.func = f
def __call__(self, *args, **kwargs):
res, a, p = self.inner_func(*args, **kwargs)
return self.func(res, self.outer_func(*a, **p))
return Decorator
def f(a, b, c, d, e, f, g, h, i, j, k, l, m, n):
return a * b * c * d * e * f * g * h * i * j * k * l * m * n
def g(o, p, *args, **kwargs):
return o+p, args, kwargs
@combines(f, g)
def h(a, b): # a is g's result
return a * b # b is f's result
print h(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) # output: 2
编写一个函数来确定哪些参数将传递给哪个函数会很困难,如果不是不可能的话,但我认为这应该可行。
【讨论】:
以上是关于创建一个结合两个函数而不指定原始函数的调用签名的装饰器的主要内容,如果未能解决你的问题,请参考以下文章