jacquev6
jacquev6

Reputation: 632

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

Answers (2)

wjandrea
wjandrea

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

Martijn Pieters
Martijn Pieters

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

Related Questions