Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@decorators in Python: why the inner defined function?

I'm just starting with Python and I have just been exposed to decorators. I wrote the following code, mimicking what I am seeing, and it works:

def decorator_function(passed_function):
    def inner_decorator():
        print('this happens before')
        passed_function()
        print('this happens after')
    return inner_decorator

@decorator_function
def what_we_call():
    print('The actual function we called.')

what_we_call()

But then I wrote this, which throws errors:

def decorator_function(passed_function):
    print('this happens before')
    passed_function()
    print('this happens after')

@decorator_function
def what_we_call():
    print('The actual function we called.')

what_we_call()

So, why do we need to have that inner nested function inside the decorator function? what purpose does it serve? Wouldn't it be simpler to just use the syntax of the second? What am I not getting?

The funny thing is that BOTH have the same (correct) output, but the second on has error text as well, saying "TypeError: 'NoneType' object is not callable"

Please use language and examples suitable for someone just starting with Python, his first programming language - and also new to OOP as well! :) Thanks.

like image 346
Sindyr Avatar asked Aug 24 '15 23:08

Sindyr


1 Answers

The reason is that when you wrap what_we_call in decorator_function by doing:

@decorator_function
def what_we_call():
    ...

What you're doing is:

what_we_call = decorator_function(what_we_call)

In you first example it works because you don't run the inner_function actually, you only initialise it, and then you return the new inner_function back (that you will call later when call the decorated what_we_call):

def decorator_function(passed_function):
    def inner_decorator():
        print('this happens before')
        passed_function()
        print('this happens after')
    return inner_decorator

Contrarily, in your second example you're going to run 2 print statements and the passed_function (what_we_call in our case) in the between:

def decorator_function(passed_function):
    print('this happens before')
    passed_function()
    print('this happens after')

In other words, you don't return a function in the example of before:

what_we_call = decorator_function(what_we_call)

You run the code (and you see the output), but then decorator_function returns 'None' to what_we_call (overwriting the original function), and when you call 'None' as if it was a function Python complains.

like image 158
matteo Avatar answered Oct 05 '22 23:10

matteo