Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 896
jacquev6 Avatar asked Apr 28 '15 12:04

jacquev6


People also ask

What does wraps do in Functools?

wraps() is a decorator that is applied to the wrapper function of a decorator. It updates the wrapper function to look like wrapped function by copying attributes such as __name__, __doc__ (the docstring), etc. Parameters: wrapped: The function name that is to be decorated by wrapper function.

What does Functools do in Python?

Functools module is for higher-order functions that work on other functions. It provides functions for working with other functions and callable objects to use or extend them without completely rewriting them. This module has two classes – partial and partialmethod.

What is from Functools import wraps?

functools. wraps is convenience function for invoking update_wrapper() as a function decorator, when defining a wrapper function. It is equivalent to partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated). So @wraps decorator actually gives a call to functools. partial(func[,*args][, **keywords]).

Which keyword is used for decorating a function?

We simply use the @ symbol before the function we'd like to decorate. Let's show that in practice below.


1 Answers

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
like image 122
wjandrea Avatar answered Sep 19 '22 20:09

wjandrea