使用 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
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