Error occuring when I try to run decorator with @ – Python

Question:

I am having a problem with the following programm.
When I try to run decorator the easier way, using @ like this

def decorator1(fun):
    def wrapper():
        text = '------'
        return text + 'n' + fun + 'n' + text

    return wrapper()


def decorator2(fun):
    def wrapper():
        return fun.upper()

    return wrapper()

@decorator1
@decorator2
def function():
    return "Hey ya!"


print(function())

Following problems occurs:

Traceback (most recent call last):
  File "C:Python_projectsmain.py", line 17, in <module>
    def function():
  File "C:Python_projectsmain.py", line 13, in decorator2
    return wrapper()
  File "C:Python_projectsmain.py", line 11, in wrapper
    return fun.upper()
AttributeError: 'function' object has no attribute 'upper'

or when I switch the order of decorators then it goes like this:

Traceback (most recent call last):
  File "C:Python_projectsmain.py", line 17, in <module>
    def function():
  File "C:Python_projectsmain.py", line 6, in decorator1
    return wrapper()
  File "C:Python_projectsmain.py", line 4, in wrapper
    return text + 'n' + fun + 'n' + text
TypeError: can only concatenate str (not "function") to str

When I run the code in this way, then it works just fine:

def decorator1(fun):
    def wrapper():
        text = '------'
        return text + 'n' + fun + 'n' + text

    return wrapper()

def decorator2(fun):
    def wrapper():
        return fun.upper()

    return wrapper()

def function():
    return "Hey ya!"
 

print(decorator(decorator2(function())))

But it seems like using @ with decorators is much more popular. Do you have any idea what I am doing wrong?

Asked By: Zeltrax

||

Answers:

First of all, a decorator is a function that accepts a function and returns a function. Thus, do not call the wrapper, but return it. Moreover, the fun method should be called.

def decorator1(fun):
    def wrapper():
        text = "------"
        return text + "n" + fun() + "n" + text

    return wrapper


def decorator2(fun):
    def wrapper():
        return fun().upper()

    return wrapper


@decorator1
@decorator2
def function():
    return "Hey ya!"


print(function())

Note that those decorators will work only with functions (or callables in general) that return str and accept no arguments. Additionally, you can annotate the fun argument properly with Callable[[], str]:

from typing import Callable


def decorator1(fun: Callable[[], str]):
    ...
Answered By: Paweł Rubin

A decorator is a function which is run on another function, as in a function object, not the result of another function.
So:

@decorator
def func():
    ...

Is equivalent to:

def func():
    ...
func = decorator(func)

Notice that the function func has never actually been called.

Therefore, within the wrapper function within the decorator, you actually need to call the function yourself. Use, for example, fun().upper(), not fun.upper().

Also, the wrapper function should never be called within the decorator; the function itself should be returned, so return wrapper not return wrapper().

Here is the corrected code:

def decorator1(fun):
    def wrapper():
        text = '------'
        return text + 'n' + fun() + 'n' + text

    return wrapper


def decorator2(fun):
    def wrapper():
        return fun().upper()

    return wrapper

@decorator1
@decorator2
def function():
    return "Hey ya!"


print(function())
Answered By: Lecdi
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.