def decorated(f):
@functools.wraps(f)
def wrapper():
return f()
return wrapper
@decorated
def g():
pass
functools.wraps
does its job at preserving the name of g
:
>>> g.__name__
'g'
But if I pass an argument to g
, I get a TypeError
containing the name of the wrapper:
>>> g(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: wrapper() takes no arguments (1 given)
Where does this name come from? Where is it preserved? And is there a way to make the exception look like g() takes no arguments
?
The name comes from the code object; both the function and the code object (containing the bytecode to be executed, among others) contain that name:
>>> g.__name__
'g'
>>> g.__code__.co_name
'wrapper'
The attribute on the code object is read-only:
>>> g.__code__.co_name = 'g'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: readonly attribute
You'd have to create a whole new code object to rename that, see a previous answer of mine where I defined a function to do that; using the rename_code_object()
function on your decorated function:
>>> 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)
Note, however, that this will entirely mask what code was being run! You generally want to see that a decorator wrapper was involved; it is the wrapper that throws the exception, not the original function, after all.