Reputation: 3823
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
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
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
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
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