How to understand python decorator for beginners

Decorator

(This is a personal study note, the content is for reference only, please indicate the author for reprinting, please correct me if there is any error.)

I remember that I learned this before and read a lot of articles about python decorators. I still don't know much about decorators. Later, in some examples, I finally figured out what decorators are through the simplest methods? The following are personal notes on decorators.

What is a decorator?

Before learning decorators, we must figure out a little bit, what exactly is a decorator? In words, the decorator is actually to add a function to a function, or a program. And considering that the function or program has been referenced, we cannot modify the function or program itself, so with the decorator, we can imagine that we have the axe and the ability to cut trees. This is the decorator effect.

The decorator needs to meet the following two points:

  1. Do not modify the code of the original program or function
  2. Does not change the calling method of functions or procedures

ps: If you don't understand here, we also use people to cut trees. For example, Xiao Ming is an individual. If you want him to cut trees, how can he cut trees? You must let him have the ability to cut trees. Then the question is, how can he have this ability? Okay, you can inject him with medicine to make him stronger, but this does not satisfy the first point above. After being transformed, we don’t know him anymore. We don’t know how to let him cut trees, so there must be prerequisites. Condition 1, not to modify the source code. Second, do not change the calling method. Originally, I wanted Xiao Ming to cut down the tree, so I ordered Xiao Ming, "Hey Xiao Ming, go cut a tree", but Xiao Ming can’t, what should I do? Let someone who knows take Xiao Ming, what will this process be? What? You let someone take Xiao Ming instead of you let Xiao Ming go, and this leads to a change in the calling method.

Understand in examples

Step 1: Prelude to the decorator

Define a test function that returns the input value

def test1(a):
	return a

The function defined above, its role is to return the input value, then we should consider adding a function to calculate its own running time. The simplest idea is to record start time -> run function -> record end time. Subtracting the start time from the end time is the result we want, as follows:

import time
start_time = time.time()	# 开始时间
test1(250)					# 要测试的函数
stop_time = time.time()		# 结束时间
delta_time = stop_time - start_time    # 时间间隔,即运行时间

Well, it is simply achieved. According to requirements, this process should be encapsulated in a function, and the running time of test1 should be returned after one call. It is stuffed into the function like this:

def test2()
	start_time = time.time()	# 开始时间
	test1(250)					# 要测试的函数
	stop_time = time.time()		# 结束时间
	delta_time = stop_time - start_time    # 时间间隔,即运行时间
	print('{:f}'.format(delta_time))  	   # 格式化打印

Run it

>>> test2(250)
'0.000001'

As you can see here, after encapsulating the above process into test2, you can get the running time of test1 function by calling test2, but it is not difficult to find two points:
(1) The calling method is changed. According to the description of the decorator, it should be Call test1 directly to get the running time
(2) Only test1 (250) is studied here, that is to say, test1 has only one fixed parameter at this time. There is no guarantee that the running time will be different due to different parameters.

Further improvements to these two points

First, let's solve the problem that the parameters are no longer fixed when calling, as follows:

def test2(func):
	def test3(x):
		start_time = time.time()	# 开始时间
		func(x)						# 要测试的函数	
		stop_time = time.time()		# 结束时间
		delta_time = stop_time - start_time    # 时间间隔,即运行时间
		return '{:f}'.format(delta_time)  	   # f返回格式化时间
	return test3

def test1(a):
	return a

Run it

>>> test2(test3)(250)
'0.000002'

There are now three functions test1, test2, test3. The whole process still needs to be sorted out clearly. It is to calculate the running time of test1, so it is the process of " record start time -> run function -> record end time ".

In the above code, the function address of test1 is first passed into test2 as a parameter . At this time, test2(func) is equivalent to test2(test1) . In test2 , another test3 is defined . Put the above-mentioned running time process into it, you can see a func(x) in test3 , func is the address we passed in, which is test1 . To calculate the running time of test1 , it must be called, func() It is func call, func (the X-) equivalent test1 (the X-) .

Let's look at the return value of the test2 function. It is the address of test3 . On the one hand, it will not report an error due to the call, but just take the address of the test3 function; on the other hand, it is to obtain test3 to ensure subsequent expansion. Now the purpose is very clear. Call test2 , pass in the address of test1 , and because it returns the address of test3 , in fact, after a meal operation, test2(test1) is like the address of test3 , and test2(test1)() is instantiating test3 , that is test3() , it is obvious that x is the 250 originally passed in. Now you can get the running time of test1 in various parameters like this.

The second step is to implement the decorator

Just talked about two points, one has been solved, then go to solve the calling method. Add @test2 to the test1 function, which is syntactic sugar, and the effect is as follows:

def test2(func):
	def test3(x):
		start_time = time.time()	# 开始时间
		func(x)						# 要测试的函数	
		stop_time = time.time()		# 结束时间
		delta_time = stop_time - start_time    # 时间间隔,即运行时间
		return '{:f}'.format(delta_time)  	   # f返回格式化时间
	return test3

@test2
def test1(a):
	return a

Run it

>>> test1(250)
'0.000001'

Calling test1 directly after adding syntactic sugar gives the desired result. Then the role of this syntactic sugar is obvious. It replaces test2(test1).

That is to say, test2(test1)(250) is directly omitted from syntactic sugar to test1(250) . The two have the same effect, but the calling method is not changed, and we have not modified the source code in test1 from beginning to end.

This is the decorator. (2018.11.16)

Guess you like

Origin blog.csdn.net/qq_39177678/article/details/106295354