Search code examples
pythondecoratorpython-decoratorsfunctools

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?


Solution

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