Most Python users will almost certainly use Python decorators once you get past the introductory stage of learning the language. It is often used in many places, such as web development, log processing, performance collection, access control, and more.
This article we will introduce Python decorators in the following four aspects:
What is a decorator?
Why use decorators?
Why did Python introduce decorators?
What problems can decorators help to solve?
Simple application of decorator: time timer.
Decorators, as the name suggests, enhance the useability of a function or class. Decorators can make a Python function have actions that they don’t normally have. That is, you can use decorators to make a plain function more powerful and beautiful.
Decorator is used to enhance the function of a function in some way. Of course, this can be done in many ways, but the decorator has a strong advantage - simplicity. You only need to add an @ above each function to enhance it.
The biggest advantage of decorators is that they are used to solve repetitive operations. The main use scenarios are as follows:
Calculate function running time
Log function
Type check
Of course, if you encounter other scenes of repeated operations, you can use the decorator as an analogy.
Look at this code:
1
2
3
4
5
6
7
8
9
10
11
12
def func1():
print('func1......')
def func2():
print('func2......')
def func3():
print('func3......')
func1()
func2()
func3()
Three functions are defined in the code, and then these three functions are called respectively. Suppose we find that the code runs very slowly, and we want to know how much time each function takes to run.
Simple solution
We can add timing code to each function:
Record start time of the first line;
Record the end time of the execution of the business logic;
Calculate the execution time of the function as the end time minus the start time.
The code is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import time
def func1():
start = time.time()
print('func1......')
end = time.time()
cost_time = end - start
print("func1 run time {}".format(cost_time))
def func2():
start = time.time()
print('func2......')
end = time.time()
cost_time = end - start
print("func2 run time {}".format(cost_time))
def func3():
start = time.time()
print('func3......')
end = time.time()
cost_time = end - start
print("func3 run time {}".format(cost_time))
func1()
func2()
func3()
This method is feasible, but this method is very cumbersome. In each function, you need to get the start time start, the end time end, the cost_time of the calculation, and an output statement. There must be a better solution!
A better solution is to use decorators. Decorator does not have any advanced syntax, it is a function that implements adding decoration to existing functions, nothing more!
The way to use decorator is like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import time
def timer(func):
# Decorator that counts the running time of functions
def wrapper():
start = time.time()
func()
end = time.time()
cost_time = end - start
print(f '{func.__name__} used {cost_time}')
return wrapper
def func1():
print('func1......')
def func2():
print('func2......')
def func3():
print('func3......')
timed_func1 = timer(func1)
timed_func2 = timer(func2)
timed_func3 = timer(func3)
timed_func1()
timed_func2()
timed_func3()
The timer function above is a decorator. Its parameters are functions that need to be decorated. The return value is a newly defined function that wraps the original function. The newly defined function first records the start time, calls the decorated function, and then calculates how much time it took. Actually, the original function is wrapped, and the original function code is not changed, and it plays a decorative role on the outside. This is the legendary decorator.
But we still need to modify the place where the function is called, which doesn’t seem to be concise enough. Is there a better way? Of course, there is.
We can use @ to specify the decorator before the decorated function. In this way, there is no need to modify the calling place, and the code is clean. When running the program, the Python interpreter will automatically generate a decorator function based on the @ annotation and call the decorator function.
The code is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import time
def timer(func):
# Decorator that counts the running time of functions
def wrapper():
start = time.time()
func()
end = time.time()
cost_time = end - start
print(f '{func.__name__} used {cost_time}')
return wrapper
@timer
def func1():
print('func1......')
@timer
def func2():
print('func2......')
@timer
def func3():
print('func3......')
func1()
func2()
func3()
This is how decorators are written; by writing a decorator timer function that counts time as a parameter of the decorator, and then returns a function wrapper that counts time. This is called a closure in professional terms. Then add @timer to each function to call this decorator to count the time of different functions.
It can be seen that the four lines of code for statistical time are repeated. A function requires four lines. If 100 functions are used, 400 lines are required. Only a few lines of code are needed to implement a decorator, and then a command is added in front of each function. That is, if it is 100 functions, the amount of code can be reduced by about 300 lines.
Through the above simple entry, you probably have already felt the magical charm of decoration. However, the use of decorators is much more than that. There are also some advanced applications of decorators. I will introduce the advanced usage of decorators later.