Josh
Josh

Reputation: 3823

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?

Upvotes: 2

Views: 1268

Answers (4)

Anentropic
Anentropic

Reputation: 33853

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

Upvotes: 4

Chris
Chris

Reputation: 22963

The difference is that with the parenthesis, you are not returning the decorator function. Rather, you are returning the return value of the function, which means you prematurely call the decorated function. If you didn't have parenthesis, your code would work correctly, and the function object inner would be returned.

In short, don't call the function. You need to return the actual function itself for your decorator to work correctly:

>>> def stars(func):
        def inner():
            print("*" * 50)
            func()
            print("*" * 50)
        return inner # Don't call inner, return it.

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


>>> func()
**************************************************
Decorate this message.
**************************************************
>>> 

If you called inner anyway, Your message would've been printed when func() was defined instead of func() being called.

Upvotes: 1

Moses Koledoye
Moses Koledoye

Reputation: 78554

By calling the inner function, instead of returning the function object, func is called at decoration time.

stars(func) -> inner() -> func() # with some side effects

Which is usually not what we want. The function is decorated to be called at a later time.

More so, in the case when your function takes argument(s), this is certainly not going work, as you'll need to actually call the decorated function with valid parameters to have them passed to inner and then on to func.

Upvotes: 2

Sean Parsons
Sean Parsons

Reputation: 762

Your invoking the inner function instead of returning it. The code below wraps the func function instead of calling it immediately like the code you've pasted above.

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

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

Upvotes: 1

Related Questions