The popular understanding of Python decorators

Source: Internet
Author: User
Tags wrapper python decorator

In the course of learning Python, I believe that there are a lot of people like me, the Python decorator has been very confused, I have been puzzled for a long time, and through thinking and review to understand, I hope the following content will be helpful to you, I also try to make a more thorough understanding of the Python decorator in a popular way, and many people have difficulty understanding the adorner because the following three points are not clear:


Understanding of the function "variable" (or "variable" function)

On the understanding of higher order functions

About the understanding of nested functions


So if you can overcome the above problems, and follow the basic principles of the adorner, I believe there will be a good understanding of the adorner. So let's first look at the purpose and principles of the following adorners.


1, the decorative device

Adorner is actually to add functionality to a program, but the program has been online or has been used, then can not be large-scale modification of the source code, this is not scientific and unrealistic, because it produced an adorner, so that it satisfies:


Cannot modify the source code of the decorated function

Cannot modify the calling method of the decorated function

Add functionality to the program to meet the above conditions

It is our goal to meet the three-point principle on demand. Because, let's start with this three-point principle to understand the adorner.


Wait, I'm going to tell you before the requirements. The principle of adorners consists of:


< function + argument higher order function + return value higher order function + nested function + syntax sugar = adorner >

This formula is the soul of the decorator!


2, the realization of the demand

Suppose you have code:

Import Timedef Test (): Time.sleep (2) print ("Test is running!") Test ()

Obviously, the result of this code operation must be: Wait about 2 seconds after the output

Test is running

Then it is required to add the statistic run time (2 second) function to the program on the basis of satisfying the three principles.

Before we move on, let's take a look at the reason for the beginning of the article 1 (Understanding of the function "variable" (or "variable" function)


2.1. Function "Variable" (or "variable" function)

Suppose you have code:

x = 1y = Xdef test1 (): Print ("Do something") Test2 = lambda x:x*2

So in memory, it should be like this:


Obviously, the function and the variable are the same, all "a name corresponds to some content in the memory address"

So according to this principle, we can understand two things:


Test1 represents the memory address of the function

Test1 () is a call to the contents of this address in test1, which is the function


If these two questions are understandable, then we can go to the next cause (understanding of higher-order functions)


2.2 Higher order functions

There are two possible forms for higher-order functions:


Pass a function name as an argument to another function ("argument higher order Function")

The return value contains the function name ("Return value higher order function")

So the name of the function in this case is actually the address of the function, it can also be considered a label of the function, not a call, is a noun. If the function name can be used as an argument, that is, you can pass the function to another function, and then do some operations in another function, according to these analysis, this is not satisfied with the adorner three principle of the first, that is, do not modify the source code and add functionality. then we look at the concrete way:


Or for the code above:


Import Timedef Test (): Time.sleep (2) print ("Test is running!") def deco (func): start = Time.time () func () #2 stop = Time.time () print (Stop-start) Deco (test) #1

Let's take a look at this code, in # #, we pass the test as an argument to the parameter func, the Func=test. Note that the address is passed here, which is where Func also points to the function body defined by the previous test, which can be said to be inside the Deco (), and Func is the test. In the # # place, the function name is appended with parentheses, which is the call to the function (execute it). Therefore, the result of this code operation is:

Test is running! The run time is 3.0009405612945557


We see that there seems to be a need to execute the source program while also attaching the timing function, but this only satisfies principle 1 (Cannot modify the source code of the decorated function), but this modifies the invocation method. Assuming that the calling method is not modified, in such a program, the adornment function cannot be passed to another adornment function.


Then think again, if you do not modify the calling method, it is necessary to have the test () this statement, then used the second high-order function, that is, the return value contains the function of the name


The following code:


Import Timedef Test (): Time.sleep (2) print ("Test is running!") def deco (func): Print (func) return func t = Deco (test) #3 #t () #4test ()

We look at this code, in the # # place, put Test into Deco (), after the operation in the Deco (), finally returned Func, and assigned to T. So here test = func and T, are the same function body. Finally, the original function call method is preserved in # #.

There is obviously a bit of confusion here, our demand is not to calculate the function run time, how to change to the output function address. This is because the second Zhang Gaojie function (which contains function names in the return value) is used alone, and it is not timed to preserve the way the original function was called. If you are timing in Deco (), you will obviously execute it once, and the test () will be executed on the outside. Just to illustrate the idea of the second high-order function, the following is really going into the play.


2.3 Nested functions

Nested functions refer to defining a function inside a function, not a call, such as:


Def func1 ():

Def FUNC2 ():

Pass

Instead of

Def func1 ():

Func2 ()

Another digression is that a function can only invoke variables or functions that are the same level as the parent. That is, the inside of the can call and it indents the same and his external, and the internal is unable to invoke.


So we'll go back to our previous requirement, and we want to count the program run time and meet the three principles.


Code:


Import timedef Timer (func) #5 def deco (): start = Time.time () func () stop = Time.time () p    Rint (Stop-start) return deco def Test (): Time.sleep (2) print ("Test is running!") Test = timer (test) #6 test () #7

This code may be a little confused, how suddenly so much more, for a moment to accept it, analysis of why this is the case.


First, the test is passed as a parameter to the timer () at the point where the timer () is inside, func = test, then a Deco () function is defined, and when it is not called, it is saved in memory, and the label is Deco. At the end of the timer () function, return the address of Deco () Deco.


Then the Deco assigned to the test, then the test is not the original test, that is, the original function of the test of the label changed, replaced by Deco. Then the call in the # # is actually Deco ().


The code, in essence, modifies the calling function, but does not modify the calling method on the surface, and implements additional functionality.


Then the popular understanding is:

Think of the function as a box, test is a small box, Deco is a box, and a timer is a large box. In the program, pass the small box test to the box Deco in the big box Temer, and then take the box Deco out and open it to see (call)


The reasons for this are:


We want to keep test () and time, and test () can only be called once (call two run results will change, not satisfy), and then according to the function as "variable", then the function can be closed back and forth. So, think of, the test passed to a function, and this function happens to embed an inner function, and then according to the scope of the inline function (can access the same level and above, the inline function can access the external parameters), the test package in the inside of the function, together return, and finally call the return function. When test passes in and then gets wrapped up, it's obvious that the test function is not lost (in the package), and the remaining test tag on the outside can replace the package (containing test ()).

At this point, everything is the same, done, a single step.


3, the real decorative device

According to the above analysis, when decorating the adorner, it is necessary to precede each function with:


Test = timer (test)


Obviously a bit of a problem, Python provides a syntactic sugar, namely:


@timer


These two sentences are equivalent, as long as the function before the addition of this sentence, you can achieve decorative effect.


The above is a non-parametric form


4, the decoration has the parameter function

Import timedef Timer (func) def deco (): start = Time.time () func () stop = Time.time () prin       T (Stop-start) return deco @timerdef Test (parameter): #8 time.sleep (2) print ("Test is running!") Test ()

For a practical problem, often there are parameters, if you want to add parameters to the modified function in the # #, obviously this program will error. The reason for the error is that test () is missing a positional parameter at the time of the call. And we know test = Func = Deco, so test () =func () =deco ()

, then when test (parameter) has parameters, Func () and Deco () must also be added parameters, in order to make the program more extensible, so the Deco () and Func () in the adorner, plus the variable parameters *agrs and **kwargs.


The complete code is as follows:


Import timedef Timer (func) def deco (*args, **kwargs): start = Time.time () func (*args, **kwargs) Stop = Time.time () print (Stop-start) return deco @timerdef Test (parameter): #8 time.sleep (2) print ("TEs       T is running! ") Test ()

So let's consider a question, what if the result of the original function test () has a return value? Like what:


def test (parameter): Time.sleep (2) print ("Test is running!") Return "returned value"

So in the face of such a function, if decorated with the above code, the last line of test () is actually called Deco (). One might ask, "func () is not the test (), why not return the value?"


There is a return value, but the return value is returned to the interior of the Deco () instead of the return value of the test (), the Deco (), then the value of func () is returned, so it is:


def timer (func) def deco (*args, **kwargs): start = Time.time () res = func (*args, **kwargs) #9 sto p = time.time () print (Stop-start) return res#10 return deco

Where the #9的值在 the # # Place to return.


The complete program is:


Import timedef Timer (func) def deco (*args, **kwargs): start = Time.time () res = func (*args, **kwargs) Stop = Time.time () print (Stop-start) return res return DECO@TIMERDEF Test (parameter): #8 TIME.S       Leep (2) print ("Test is running!") Return "returned value" test ()

5, with parameters of the adorner

Added a demand, an adorner, different decorations for different functions. Then you need to know which function to take what kind of decoration. Therefore, the adorner needs to be labeled with a parameter. For example:

@decorator (parameter = value)


For example, there are two functions:


Def task1 (): Time.sleep (2) print ("In the Task1") def task2 (): Time.sleep (2) print ("In the Task2") Task1 () Task2 ()

To count the two functions separately, the elapsed time is counted, but the statistics are required to output:


The Task1/task2 run time is:2.00 ...

So it is necessary to construct a decorator timer, and need to tell the adorner which is Task1, which is task2, that is to say:


@timer (parameter= ' Task1 ') #def Task1 (): Time.sleep (2) print ("In the Task1") @timer (parameter= ' task2 ') #def Task2 (): Time.sleep (2) print ("In the Task2") Task1 () Task2 ()

Then the method has, but we need to consider how to pass this parameter parameter to the adorner, our previous decorator, is the transfer function name in, and this time, more than one parameter, how to do it?

So, the thought of adding a layer of functions to accept parameters, according to the concept of nested functions, to execute the inner function, you must first execute the outer function, in order to call into the inner function, then there are:


def timer (parameter): # print ("In the Auth:", parameter) def Outer_deco (func): # Print ("in the Outer_wrapper : ", parameter) def deco (*args, **kwargs): Return deco return Outer_deco

First timer (parameter), receive the parameter parameter= ' TASK1/2 ', and @timer (parameter) also happens to have parentheses, then will execute this function, then is equivalent to:


Timer = timer (parameter) Task1 = Timer (TASK1)

The later runs are the same as the usual adorners:


Import timedef timer (parameter):     def outer_wrapper (func):         def wrapper (*args, **kwargs):             if parameter ==  ' Task1 ':                 start = time.time ()                  func (*args, ** Kwargs)                 stop  = time.time ()                  print ("THE&NBSP;TASK1&NBSP;RUN&NBSP;TIME&NBSP;IS&NBSP;:",  stop - start)              elif parameter ==  ' Task2 ':                  start = time.time ()                  func (*args, **kwargs)                  stop = time.time ()                 print ("the  task2 run time is : ",  stop - start)          return wrapper    return outer_wrapper@timer (parameter= ' Task1 ' ) Def task1 ():     time.sleep (2)     print ("In the task1") @timer (parameter= ' Task2 ') Def task2 ():     time.sleep (2)     print (" In the task2 ") Task1 () Task2 ()

At this point, the entire content of the adorner ends.


The popular understanding of Python decorators

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.