In the previous article “Introduction to Python Decorator”, we introduced what decorators are, why decorators are used, and the simple application of decorators. This article will introduce the advanced application of the decorator.
Remember the example of the timer decorator mentioned in the previous article? That is the simplest example. The decorated function has neither parameters nor return values. Let’s look at the situation with parameters and return values.
Let’s modify func1 and pass in a parameter, which means how many steps should be taken.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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(num):
print(f 'I walked {num} steps')
func1(3)
It will report an error when running.
This is because, on the surface, we wrote func1(3), but in fact Python calls the wrapper () function first. This function does not accept parameters, so an error was reported.
In order to solve this problem, we only need to add parameters to the wrapper.
The wrapper uses wildcards, *args represents all positional parameters, and **kwargs represents all keyword parameters. In this way, any parameter situation can be dealt with. When the wrapper calls the decorated function, just pass in the parameters intact.
If the decorated function func has a return value, the wrapper only needs to return the return value of func.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import time
def timer(func):
# Decorator that counts the running time of functions
def wrapper( * args, ** kwargs):
start = time.time()
end = time.time()
cost_time = end - start
print(f '{func.__name__} used {cost_time}')
result_value = func( * args, ** kwargs)
return result_value
return wrapper
@timer
def func2(num1, num2):
return num1 + num2
sum = func2(3, 4)
print(sum)
In the wrapper function, we first save the return value of func to result_value, and then return this value at the end of the wrapper.
After the previous example, we can summarize a decorator template. According to this template, you can easily write a decorator:
1
2
3
4
5
6
7
def decorator(func):
def wrapper_decorator( * args, ** kwargs):
#Operation before calling
result_value = func( * args, ** kwargs)
#Operation after called
return result_value
return wrapper_decorator
Note that the parameters here refer to the parameters of the decorator, which are different from the parameters of the function itself mentioned earlier.
Let’s look at an example first. This uses a decorator to slow down the execution time of the program.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import time
def decorator_slow(func):
def wrapper( * args, ** kwargs):
print(f '{func.__name__} sleeping 0.5 second')
time.sleep(0.5)
ret_val = func( * args, ** kwargs)
return ret_val
return wrapper
@decorator_slow
def func2(num1, num2):
return num1 + num2
sum = func2(3, 4)
print(sum)
The slow decorator above is now fixed at 0.5 seconds. It would be nice if you could sleep as you like. We need to use decorators with parameters. To make the decorator accept parameters, you need to put a layer of functions on the outside of the ordinary decorator.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import time
def slow(seconds):
def decorator_slow(func):
def wrapper( * args, ** kwargs):
print(f '{func.__name__} sleeping {seconds} second')
time.sleep(seconds)
result_value = func( * args, ** kwargs)
return result_value
return wrapper
return decorator_slow
@slow(2)
def func2(num1, num2):
return num1 + num2
sum = func2(3, 4)
print(sum)
The previous decorator was a two-level function, now this is a tri-level function:
Slow, accepts “seconds” as a parameter;
The decorator_slow function is created in slow;
A wrapper function is created in decorator_slow.
In fact, the latter two layers are the same as before. The only difference is that another layer is added to the outside.
Why doesn’t the outermost layer need to pass in the func parameter?
This is because when Python finds that the slow (2) decorator has its own parameters, it no longer passes in the current function as a parameter, and directly calls slow. This is specified by the Python interpreter.
Slow returns a function. At this time, Python will pass in the current function again, which becomes an ordinary decorator.
This means that the function of the outermost layer is to process the parameters of the decorator.
The previous decorators are used to decorate functions, or to decorate class methods, such as timer, slow, and staticmethod, and classmethod that come with Python.
What if you put the decorator before the class name? Look at this code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from slow
import slow
@slow(1)
class Counter():
def __init__(self):
self._count = 0
def printer(self):
self._count += 1
print(f 'print times: {self._count}')
c1 = Counter()
c1.printer()
c1.printer()
c2 = Counter()
c2.printer()
c2.printer()
We have built a class Counter() for counting. It has an internal variable called _count. Every time the printer() method of Counter is called, the count will increase by one.
In front of Counter(), we introduced the slow decorator written earlier. This time @slow is placed in front of the Counter class name instead of the method. What will happen? Run the above code, you will find this result:
This means that only when the Counter instance is created, it sleeps for one second, and when the printer function is called, it does not sleep.
Therefore, the class decorator actually decorates the initialization method of the class. It will only be decorated once during initialization.
Can a function only have one decorator? The essence of the decorator is to call the decorator first, and then the decorator calls the function. In this case, it’s okay to call a few more layers.
Look at this example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import time
from slow
import slow
def timer(func):
def wrapper():
start_time = time.perf_counter()
func()
end_time = time.perf_counter()
cost_time = end_time - start_time
print(f '{func.__name__} used {cost_time}')
return wrapper
@slow(0.5)
@timer
def func1():
print('Well done!')
func1()
In this example, the func() function uses two decorators, slow and timer. Call from top to bottom, first call slow, then slow calls timer, and then timer calls func1, so the execution result is:
In summary, the role of the decorator is to enhance the function of the function. To be precise, you can decorate the function and you can also decorate the class. The decorator can pass parameters or not.
In actual development, decorators are still very common and useful. Mainly used in several aspects: parameter checking, statistical time, caching, registered callback function, function plus log, thread asynchronous, etc.
Decorators are important and varied. The official Python documentation has compiled a list of decorators. You can search PythonDecoratorLibrary on the search engine.