Search code examples
pythonpython-3.xreturnpython-decorators

Python Decorators & Nested Functions return statement. Proper usage with parenthesis?


What's the difference including/not including the parentheses on the return statement for inner function for decorators (and any nested functions)? Are the parenthesis only required if the function returns more than 1 variable?

In the example below I know this won't work unless I use return inner(), but I see other examples that don't use the () on the return and they work fine.

Terminology behind the reason would great so help me fully understand. Thanks.

def stars(func):
    def inner():
        print("*" * 50)
        func()
        print("*" * 50)
    return inner()

@stars
def func():
    print('Decorate this message.')

It initially prints my message, decorated. But if I call func() again, it will not print anything.

Another working example, but return inner works fine in this case, long as I have set up my function to support a callable variable, being msg for my example:

def star(func):
    def inner(*args, **kwargs):
        print("*" * 30)
        func(*args, **kwargs)
        print("*" * 30)
    return inner

@star
def func(msg):
    print(msg)
func("Another message to be decorated")

And I can call func('my new message') each time to print my message, decorated. Why is this compared to the previous example when I don't use a callable function?


Solution

  • In a decorator you should not do return inner()

    That would mean you are replacing the decorated func with the result of calling the wrapped func

    Your code will immediately print the modified message, without you even trying to call func()!!

    **************************************************
    Decorate this message.
    **************************************************
    

    But that is not what is intended when you decorate a function.

    You intend to modify the behaviour of the function, but you still expect to have to call the function manually.

    That is why the code should be:

    def stars(func):
        def inner():
            print("*" * 50)
            func()
            print("*" * 50)
        return inner
    
    @stars
    def func():
        print('Decorate this message.')
    
    func()
    **************************************************
    Decorate this message.
    **************************************************
    

    To explain further:

    When you use the decorator syntax

    @stars
    def func():
    

    You are telling python to do the following:

    func = stars(func)
    

    In other words, def func but replace the function with the result of calling stars(func)

    You might find it less confusing if you don't reuse the name func in the code. For example:

    def stars(func):
        def inner():
            print("*" * 50)
            func()
            print("*" * 50)
        return inner
    
    @stars
    def print_a_message():
        print('Decorate this message.')
    
    print_a_message()
    **************************************************
    Decorate this message.
    **************************************************
    

    Maybe it's clearer now that when we do return inner from the decorator we are telling Python to replace print_a_message with inner