简体   繁体   中英

Why do we need wrapper function in decorators?

If I create a decorator like following:

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

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

Another decorator can be defined as:

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

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

Both decorators will work the same. What is the benefit of using "wrapper" function inside decorator. I didn't get the purpose.

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)

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM