使用 functools.wraps 修饰的函数引发带有包装器名称的 TypeError。为什么?如何避免?

Function decorated using functools.wraps raises TypeError with the name of the wrapper. Why? How to avoid?

def decorated(f):
    @functools.wraps(f)
    def wrapper():
        return f()
    return wrapper

@decorated
def g():
    pass

functools.wraps 的工作是保留 g 的名称:

>>> g.__name__
'g'

但是如果我将参数传递给 g,我会得到一个包含包装器名称的 TypeError

>>> g(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: wrapper() takes no arguments (1 given)

这个名字从何而来?它保存在哪里?有没有办法让异常看起来像 g() takes no arguments?

名称来源于代码对象;函数和代码对象(包含要执行的字节码等)都包含该名称:

>>> g.__name__
'g'
>>> g.__code__.co_name
'wrapper'

代码对象的属性是只读的:

>>> g.__code__.co_name = 'g'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: readonly attribute

您必须创建一个全新的代码对象来重命名它,请参阅 我在其中定义了一个函数来执行此操作;在装饰函数上使用 rename_code_object() 函数:

>>> g = rename_code_object(g, 'g')
>>> g(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: g() takes no arguments (1 given)

但是请注意,这将完全掩盖正在使用的代码 运行!您通常希望看到涉及装饰器包装器;毕竟抛出异常的是包装器,而不是原始函数。

涵盖了您的前两个问题,但还有一个更好的解决方案:不要对 f 的论点做出任何假设,而是将所有论点从 wrapper() 转发到 f():

import functools

def decorated(f):
    @functools.wraps(f)
    def wrapper(*args, **kwargs):  # <- Take any arguments
        return f(*args, **kwargs)  # <- Forward
    return wrapper

@decorated
def g():
    pass

g(1)

输出:

Traceback (most recent call last):
  File "/home/wja/testdir/tmp.py", line 15, in <module>
    g(1)
  File "/home/wja/testdir/tmp.py", line 8, in wrapper
    return f(*args, **kwargs)
TypeError: g() takes 0 positional arguments but 1 was given