Reputation: 632
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
?
Upvotes: 12
Views: 1358
Reputation: 33022
Martijn's answer covers your first two questions, but there's a better solution: don't make any assumptions about f
's arguments, instead forward all arguments from wrapper()
to 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)
Output:
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
Upvotes: 1
Reputation: 1122372
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.
Upvotes: 10