Understanding decorators: return type is function when no argument is specified

I use one decorator for two separate functions: one with a decorator argument specification; and one more without it.

When no optional argument is passed, the return type is a function (specifically inner_function

in a decorator). However, when an optional argument is passed, it works as expected.

Can you explain what is happening here and why it works differently in these cases?

def cache_disk(cache_folder="./cache"):
    def wrapper(f):
        def inner_function(*args, **kwargs):
            result = f(*args, **kwargs)
            return result
        return inner_function
    return wrapper

@cache_disk
def func1(data):
    return [d for d in data]

@cache_disk(cache_folder='./cache/')
def func2(data):
    return [d for d in data]


data = [1,2,3]
print(func1(data))
print(func2(data))

      

Result:

<function inner_function at 0x7f1f283d5c08>
[1, 2, 3]

      

+3


source to share


2 answers


Note that:

@decorator  # no arguments
def func(...):
    ...

      

equivalent to:

def func(...):
    ...

func = decorator(func)  # one 'level' of calls

      

So what:

@decorator(...):  # arguments
def func(...):
    ...

      

equivalent to:

def func(...):
    ...

func = decorator(...)(func)  # two 'levels' of calls

      

In the first case, there is one argument for the decorator, itself func

. In the second case, the arguments to the decorator are ...

from a string @

, and this is the function returned by the decorator that is called with func

as an argument.




In your example

@cache_disk
def func1(data):
    ...

      

the decorator cache_disk

takes one argument called ( func

which becomes args[0]

) and returns wrapper

. Then when you call:

print(func1(data))

      

wrapper

takes a single argument ( data

which becomes f

) and returns inner_function

.

Hence, you have three options:

  • Decorate func1

    with @cache_disk()

    (mark parentheses) without passing arguments << 29> and func

    on wrapper

    ;
  • Alter cache_disk

    behave differently depending on whether it passed one, a called argument, or something else; or
  • As @ o11c pointed out in the comments , use for example cache_disk.wrapper = cache_disk()

    to provide a convenient alias for the parameterless version, then decorate with @cache_disk.wrapper

    .
+3


source


if you want the default values, you need to call a function that returns the decorator:



@cache_disk()
def func1(data):
    return [d for d in data]

      

+1


source







All Articles