python - 创建一个组合两个函数的装饰器,而不指定原始函数的调用签名

标签 python python-3.x python-decorators functools function-signature

我想创建一个装饰器,它组合了两个函数并组合了它们签名中的参数。

我想要的界面:

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'

很明显我的实现并没有真正起作用。

最佳答案

您的问题是,您使用不同的参数调用 f 函数两次,一次在原始 g 中仅使用其预期参数,一次在包装器中使用所有参数.

你必须选择一个,我的建议是从原来的g中删除它的调用

我稍微改变了你的代码,但至少我的版本可以在 Python 3.5 中运行:

  • 包装函数的签名列出了 fg 的所有参数
  • 包装的函数接受位置参数和关键字参数
  • 包装函数在接收到错误数量的参数时会引发错误

这是代码:

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

关于python - 创建一个组合两个函数的装饰器,而不指定原始函数的调用签名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41982952/

相关文章:

python - 属性错误 : 'str' object has no attribute 'write'

python - 向量化切片的最小值和最大值可能吗?

python - 如何在 python tkinter 中使窗口处于空闲状态直到按下按钮?

python - 通过迭代将函数应用于数据帧的所有行 - Python

python-3.x - 如何解码文件中base64格式的编码值?

Python:为特定函数调用修补打印函数? (用于打印递归树的装饰器)

python - for循环中的自定义排序

python - 获取所有距离一个字符的字符串?

python - 装饰器适用于功能但不适用于类

python - 装饰器不调用包装函数