如何以与 inspect.signature 一起使用的方式包装 python 函数?

How can I wrap a python function in a way that works with with inspect.signature?

前面一些无争议的背景实验:

import inspect

def func(foo, bar):
  pass

print(inspect.signature(func))  # Prints "(foo, bar)" like you'd expect

def decorator(fn):
  def _wrapper(baz, *args, *kwargs):
    fn(*args, **kwargs)

  return _wrapper

wrapped = decorator(func)
print(inspect.signature(wrapped))  # Prints "(baz, *args, **kwargs)" which is totally understandable

问题

如何实现我的装饰器以便 print(inspect.signature(wrapped)) 输出“(baz, foo, bar)”?我可以通过添加传入的任何 fn 的参数,然后将 baz 粘贴到列表中,以某种方式动态构建 _wrapper 吗?

答案不是

def decorator(fn):
  @functools.wraps(fn)
  def _wrapper(baz, *args, *kwargs):
    fn(*args, **kwargs)

  return _wrapper

再次给出“(foo, bar)”——这是完全错误的。调用 wrapped(foo=1, bar=2) 是类型错误 - “缺少 1 个必需的位置参数:'baz'”

我觉得没必要这么迂腐,但是

def decorator(fn):
  def _wrapper(baz, foo, bar):
    fn(foo=foo, bar=bar)

  return _wrapper

也不是我正在寻找的答案 - 我希望装饰器适用于所有功能。

您可以使用__signature__ (PEP) 属性修改包装对象的返回签名。例如:

import inspect


def func(foo, bar):
    pass


def decorator(fn):
    def _wrapper(baz, *args, **kwargs):
        fn(*args, **kwargs)

    f = inspect.getfullargspec(fn)

    fn_params = []
    if f.args:
        for a in f.args:
            fn_params.append(
                inspect.Parameter(a, inspect.Parameter.POSITIONAL_OR_KEYWORD)
            )

    if f.varargs:
        fn_params.append(
            inspect.Parameter(f.varargs, inspect.Parameter.VAR_POSITIONAL)
        )

    if f.varkw:
        fn_params.append(
            inspect.Parameter(f.varkw, inspect.Parameter.VAR_KEYWORD)
        )

    _wrapper.__signature__ = inspect.Signature(
        [
            inspect.Parameter("baz", inspect.Parameter.POSITIONAL_OR_KEYWORD),
            *fn_params,
        ]
    )
    return _wrapper


wrapped = decorator(func)
print(inspect.signature(wrapped))

打印:

(baz, foo, bar)

如果函数是:

def func(foo, bar, *xxx, **yyy):
    pass

然后 print(inspect.signature(wrapped)) 打印:

(baz, foo, bar, *xxx, **yyy)