Python functions - decorator

1. Core concepts of Python functions

There are several core concepts about functions:
1 in Python, a function is a first-class citizen, and a function is also an object.
We can assign functions to variables, such as the following code:

def func(message):
    print('Got a message: {}'.format(message))
    
send_message = func
send_message('hello world')

# output
Got a message: hello world

The function func is assigned to the variable send_message, then call seng_. Message, which is equivalent to calling func().

2 in addition, we can pass a function as a parameter into another function, such as the following code:

def get_message(message):
    return 'Got a message: ' + message

def root_call(func, message):
    print(func(message))
    
root_call(get_message, 'hello world')

# output
Got a message: hello world

3 you can define functions in functions, that is, the nesting of functions.

def func(message):
    def get_message(message):
        print('Got a message: {}'.format(message))
    return get_message(message)

func('hello world')

# output
Got a message: hello world

4 the return value of a function can also be a function object (closure)

def func_closure():
    def get_message(message):
        print('Got a message: {}'.format(message))
    return get_message

send_message = func_closure()
send_message('hello world')

# output
Got a message: hello world

2 simple decorator

General function

import time
def i_can_sleep():
    time.sleep(2)
start_time = time.time()
i_can_sleep()
stop_time = time.time()
print("The function is running %s second"%(stop_time-start_time))

Output:
The function ran 2.000124454498291 second

If there are a lot of I_ can_ The sleep () function needs to count the running time, so it will write multiple starts_ time = time.time(),stop_time = time.time() is called to do the repeated things only once, and the decorator can be used.

def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    # Note that the function object is returned
    return wrapper

def greet():
    print('hello world')

greet = my_decorator(greet)
greet()

# output
wrapper of decorator
hello world

In this code, the variable greet points to the internal function wrapper(), and the internal function wrapper() calls greet(). Therefore, when greet is finally called, it will print "wrapper of decorator" first, then output hello world.

The function here is my_decorator() is a decorator that wraps the function greet() that needs to be executed and changes its behavior, but the original function greet() remains unchanged.

The above code is simpler and more elegant in Python:

def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper

@my_decorator
def greet():
    print('hello world')

greet()

Here @, we call it grammar sugar, @ my_decorator is equivalent to the previous greet=my_decorator(greet) statement, but more concise.

Therefore, if other functions in your program need to be decorated similarly, you only need to add @ decorator above them, which greatly improves the reuse of functions and the readability of the program.

3 trimmer with parameters

You can add corresponding parameters to the corresponding decorator function wrapper(), such as:

def my_decorator(func):
    def wrapper(message):
        print('wrapper of decorator')
        func(message)
    return wrapper

@my_decorator
def greet(message):
    print(message)

greet('hello world')

# output
wrapper of decorator
hello world

But if another function needs two arguments, use the decorator my_decorator, there is a problem with the rescue, so usually, we will Variable length parameter *args and kwargs as arguments to wrapper() of decorator's internal function* args and kwargs indicate that any number and type of parameters are accepted, so the decorator can be written in the following form:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print('wrapper of decorator')
        func(*args, **kwargs)
    return wrapper

4 decorator with custom parameters

For example, if I want to define a parameter to represent the number of times the decorator's internal function is executed, it can be written in the following form:

def repeat(num):
    def my_decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(num):
                print('wrapper of decorator')
                func(*args, **kwargs)
        return wrapper
    return my_decorator


@repeat(4)
def greet(message):
    print(message)

greet('hello world')

# Output:
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world

5 retain function meta information

Now print the function name:

print(greet.__name__)

Output:
wrapper

After the greet() function is decorated, its meta information changes. The meta information tells us that "it is no longer the previous green () function, but replaced by the wrapper() function".
In order to solve this problem, we usually use the built-in decorator @ functools.wrap, which helps to retain the meta information of the original function (that is, copy the meta information of the original function to the corresponding decorator function)

import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('wrapper of decorator')
        func(*args, **kwargs)
    return wrapper
    
@my_decorator
def greet(message):
    print(message)

print(greet.__name__)

# output
greet

Keywords: Python

Added by genius_supreme on Mon, 06 Sep 2021 06:35:59 +0300