Search code examples
pythonpython-2.7python-decorators

Python decorators with function attributes


I have been experimenting with decorators and found an interesting inconsistency with them, hope you might help me to resolve it.

To begin with I had a decorator like this:

>>> def name(n):
...     def decorator(fun):
...             fun.name = n
...             return fun
...     return decorator

and I used it like so:

>>> @name("my name jeff")
... def f():
...     print f.name

since decorator returns fun I could do both:

>>> f()
my name jeff
>>> f.name
'my name jeff'

This was all fine and what I expected. Now comes the weird bit. My new decorator is as follows:

>>> def name(n):
...     def decorator(fun):
...             fun.name = n
...             def wrapper():
...                     return fun()
...             return wrapper
...     return decorator

To me it looks like this should do the same thing as the one before it, however I get this:

>>> @name("my name jeff")
... def f():
...     print f.__name__
...     print f.name
...
>>> f()
wrapper
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "<stdin>", line 5, in wrapper
 File "<stdin>", line 4, in f
AttributeError: 'function' object has no attribute 'name'

Whats even more weird is the following:

>>> def f():
...     print f.__name__
...     print f.name
...
>>> x = name("jeff")(f)
>>> x.name
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute 'name'
>>> x()
f
jeff

Of course, x.name now fails since decorator returns wrapper and not fun. At the same time:

>>> f = name("jeff")(f)
>>> f()
wrapper
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in wrapper
File "<stdin>", line 3, in f
AttributeError: 'function' object has no attribute 'name'

Furthermore:

>>> def name(n):
...     def decorator(fun):
...             fun.name = n
...             @wraps(fun)
...             def wrapper():
...                     return fun()
...             return wrapper
...     return decorator
...
>>> @name("my name jeff")
... def f():
...     print f.__name__
...     print f.name
...
>>> f()
f
my name jeff

Im not much of a python ninja so if Im missing something obvious please point it out.


Solution

  • Your decorator is as follows:

    >>> def name(n):
    ...     def decorator(fun):
    ...             fun.name = n
    ...             def wrapper():
    ...                     return fun()
    ...             return wrapper
    ...     return decorator
    

    You're setting the name attribute for fun, but you're returning wrapper. In other words, you replace fun by wrapper, that indeed has no name attribute.

    You could try the following:

    >>> def name(n):
    ...     def decorator(fun):
    ...             def wrapper():
    ...                     return fun()
    ...             wrapper.name = n
    ...             return wrapper
    ...     return decorator
    

    Example:

    >>> @name("hello")
    ... def f():
    ...     print(f.__name__)
    ...     print(f.name)
    ...     
    >>> f()
    wrapper
    hello