Why do we need wrapper function in decorators?
Asked Answered
T

4

29

If I create a decorator like the following:

def my_decorator(some_fun):
    def wrapper():
        print("before some_fun() is called.")
        some_fun()
        print("after some_fun() is called.")
    return wrapper

@my_decorator
def just_some_fun():
    print("Wheee!")

Another decorator can be defined as:

def my_decorator(some_fun):
    print("before some_fun() is called.")
    some_fun()
    print("after some_fun() is called.")

@my_decorator
def just_some_fun():
    print("some fun")

What is the benefit of using "wrapper" function inside decorator? I didn't understand the purpose.

Toscana answered 26/7, 2017 at 19:12 Comment(4)
did you try it? These two code blocks produce completely different results for me.Vannoy
Really? Did you try calling just_some_fun() in your 2nd code block? Yes, stuff gets printed when my_decorator gets called on just_some_fun, but when you call just_some_fun() it crashes with TypeError: 'NoneType' object is not callable.Ecology
"Both decorators will work the same" - do you have any support for this assertion?Sour
Printing is a side effect and different than returning an object. Try something similar without using print() such as amending a string. You will have to return something.Duly
E
20

The purpose of having a wrapper function is that a function decorator receives a function object to decorate, and it must return the decorated function.

Your 2nd version of my_decorator doesn't have an explicit return statement, so it returns None. When my_decorator is called via the @ decorator syntax

before some_function() is called.
some fun
after some_function() is called.

gets printed, and then None gets assigned to the name just_some_fun. So if you add print(just_some_fun) to the end of that code it will print None.

It may be easier to understand what's going on if we get rid of the @ syntactic sugar and re-write your code using normal function calling syntax:

def my_decorator(some_fun):
    print("before some_function() is called.")
    some_fun()
    print("after some_function() is called.")

def just_some_fun():
    print("some fun")

just_some_fun = my_decorator(just_some_fun)
Ecology answered 26/7, 2017 at 19:49 Comment(0)
A
14

The decorators in Python are callable objects which in the simplest case is function taking one parameter which is some function or class. The decorator should return again same type which it takes (so if it takes function it should return function). The point is in the time when the decorator is called.

When you import a Python file or just run it directly the Python interpreter goes through the content and gathering information about what classes and function are defined and if it encounter on some code (not declaration) it will execute it.

If the interpreter encounter on decorator it takes the decorated function, call the decorator and replace the decorated function with returned value from the decorator.

Let's say you have this code:

@my_decorator
def my_function()
  print("My_function")

It's equivalent to this call:

def my_function()
    print("My_function")    

my_function = my_decorator(my_function)

If the decorator would be like this one

def my_decorator(func):
    print("decorated)
    return 42

then the my_function is not even a function it would be an integer (you can try print(my_function))

So when you define decorator as

def my_decorator2(some_fun):
    print("before")
    some_fun()
    print("after")

then this decorator returns nothing (in python it means it returns None).

@my_decorator2
def decorated():
  print("inside")

prints

before
inside
after

but calling decorated() will raise exception 'NoneType' object is not callable because the decorated was replaced with None.

You should always create decorator which return something useful like function or class (which is usually the "wrap" function inside). Sometimes can be useful to return from decorator something else then function/class but it usually obfuscate your code and convert it into something totally non-maintainable.

Attainder answered 26/7, 2017 at 19:59 Comment(1)
thanks for clearly explaining (with examples) why the decorated function MUST be returned (otherwise it cannot be called at all)!Grope
G
0

Why do we need wrapper function in decorators?

We do not need a wrapper function in decorators. There is a popular third-party library called decorator which allows to write decorators in the "closureless" style, quite similar to your second example.

So, for the standard decoration with a closure + wrapper function:

def my_decorator(some_fun):
    def wrapper():
        print("before some_fun() is called.")
        some_fun()
        print("after some_fun() is called.")
    return wrapper

@my_decorator
def just_some_fun():
    print("Wheee!")

An equivalent, written in the closureless style, could be done like this:

from decorator import decorator  # pip install decorator

@decorator
def my_decorator(some_fun):
    print("before some_fun() is called.")
    some_fun()
    print("after some_fun() is called.")

@my_decorator
def just_some_fun():
    print("Wheee!")

With this package, the user doesn't have to define a wrapper function explicitly. You may note: the wrapping is handled by another decorator, ironically.

Giulio answered 8/10, 2023 at 18:53 Comment(0)
A
-1

Its already explained why to use wrapper function, out of curiosity I am just giving examples what we can do if we don't need a wrapper function.

Type 1

Return a small function which returns None or pass

def decorator_func(to_be_decorated_function):
    print("Logging IN: Currrently  in function")
    to_be_decorated_function()
    print("Logging OUT: Currrently  in function")

    def a(): None  # or def a(): pass

    return (a)

@decorator_func
def to_be_decorated_function():
    print('October 16, 2000')

to_be_decorated_function()
# equivalent to 
#to_be_decorated_function = decorator_func(to_be_decorated_function)

Type 2

This merely tears out the usage of decorators, and just a slight tweak.What if we don't return , as well not use callable object at all.

def decorator_func(to_be_decorated_function):
    print("Logging IN: Currrently  in function")
    to_be_decorated_function()
    print("Logging OUT: Currrently  in function")

@decorator_func
def to_be_decorated_function():
    print('October 16, 2000')

to_be_decorated_function  # notice I'm just using the object and not callable function
# equivalent to
#decorator_func(to_be_decorated_function)

Output from both the types

Logging IN: Currrently  in function
October 16, 2000
Logging OUT: Currrently  in function
Able answered 13/7, 2019 at 7:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.