Mustafa Kareem
Mustafa Kareem

Reputation: 59

Why we don't call function when we return it in decorator or nested function

when we write function inside function why we don't call it ? how the return call it ?

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
return wrapper

why return wrapper instead of return wrapper() ? and what we do when wrapper must take arguments?

Upvotes: 1

Views: 879

Answers (2)

Matthias Fripp
Matthias Fripp

Reputation: 18625

A decorator is used to replace a function with a different one, usually one that modifies the behavior of the original function in some way. So the decorator has to return a function that should be called instead of the original one.

If you returned wrapper(), you would just call the new wrapper function once and return the result. But what you really need to do is replace func with wrapper, so that every time someone calls func, they'll actually call wrapper instead, which will then call func as needed. So my_decorator has to return wrapper itself, not the output from wrapper(). Then, when you use my_decorator as a decorator, Python will automatically call wrapper everytime instead of the original function.

The wrapper function should always take the same arguments as func. So if it needs arguments, just define it with the same arguments as func takes in the def wrapper(): line.

Upvotes: 1

Ryan Schaefer
Ryan Schaefer

Reputation: 3120

Functions can be treated like values in Python

def foo():
    print("Hello World!")
x = foo
x()

is valid Python that will print out Hello World!

Expanding on this idea... What if we wanted to do something before we called our function? Well we could do this:

def foo():
    print("Hello World!")
def do_something_before():
    print("something before")
    foo()
x = do_something_before
x()

But we have locked ourselves in to only being able to use the function foo. What if we wanted to do "something before" for bar or foobar or any number of functions?

We parameterize it! And since we are adding extra bits onto the "normal" function, i.e decorating it, let's call this new function a decorator.

def foo():
    print("Hello World!")

def do_something_before(func): # the decorating function
    def wrap():
        print("something before")
        func()
    return wrap

x = do_something_before(foo)
x()

Python then allows us to use syntactic sugar to do this wherever we want:

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def foo():
    print("Hello World!")

Decorators have to take a function in, then be allowed to be called again so instead of directly calling foo we will return the function with foo inside of it to call later when we call foo. We are actually calling wrapper, returned by foo. If we called wrapper in my decorator, we would be returning the results of wrapper rather than wrapper itself. That means we couldn't call foo() later as there is essentially no function to call. We can add arguments like this:

def my_decorator(func):
    def wrapper(*args):
        print("Something is happening before the function is called.")
        func(*args)
        print("Something is happening after the function is called.")
    return wrapper

Meaning my_decorator can now decorate any function. We could also specify a specific number of arguments.

Upvotes: 2

Related Questions