12 easy steps to handle Python adorners

Source: Internet
Author: User
Tags closure function definition wrapper python decorator

12 easy steps to handle Python adorners

Oh! As a teacher who teaches Python, I find that it is very difficult for the students to get to the Python decorator at first, perhaps because the adorner is really hard to understand. Decorating the decorator requires you to understand some of the concepts of functional programming and, of course, to understand some of the features of defining and invoking function-related syntax in Python.

I can't make the adorner easy, but with a step-by-step analysis, I might be able to make you feel more confident when you understand the decorator. Because the adorner is very complex, this article will be very long (I say very long, also dare so much nonsense blablabla ... The foreplay will not continue to translate directly omitted)

1. Functions

In Python, functions are defined by the DEF keyword, the function name, and an optional parameter list. Returns the value by the return keyword. Let's say, for example, how to define and invoke a simple function:

>>> def foo (): ...     Return 1>>> foo () 1

The method body (of course, many lines are the same) is necessary, by means of indentation, after the method name with a double parenthesis () to be able to call the function

2. Scope

In Python, the function creates a new scope. Python developers might say that a function has its own namespace, almost one meaning. This means that when a variable is encountered inside a function, the function takes precedence over its own namespace to look for. Let's write a simple function to see what the difference is between a local scope and a global scope:

>>> a_string = "This is a global variable" >>> def foo ():     ... Print locals () >>> print globals () {..., ' a_string ': ' This is a global variable '}>>> foo () # 2{}

The built-in function globals returns a dictionary containing the names of variables known to all Python interpreters (for the sake of cleanliness and washing, I omitted some of the variables that Python created by itself). In # # I called the function foo to print out the contents of the local scope inside the function. We can see that the function Foo has its own separate namespace, although there is nothing in the temporary namespace.

3. Variable parsing rules

This is not to say that we cannot access global variables outside of the function. In Python's scope rules, creating a variable is bound to create a variable in the current scope, but when the variable is accessed or modified, the variable is looked up in the current scope, and no matching variable is found, which in turn is looked up in the closed scope. So if we modify the implementation of the function foo so that it can print the global scope of the variable is also possible:

>>> a_string = "This is a global variable" >>> def foo ():     ... Print a_string # 1>>> foo () This is a global variable

In the # # place, the Python interpreter tries to find the variable a_string, which is not found in the local scope of the function, so it goes to the upper scope to find it.
But on the other hand, if we assign a value to a global variable inside a function, the result is different from what we think:

>>> a_string = "This is a global variable" >>> def foo ():     ... a_string = "Test" # 1 ...     Print locals () >>> foo () {' a_string ': ' Test '}>>> a_string # 2 ' This is a global variable '

We can see that global variables can be accessed (if they are mutable data types (like list,dict) and can even be changed) but not assigned. In the inside of the function, we actually created a local variable that hides the same name variable in the global scope. We can draw this conclusion by printing out the contents of the local namespace. We can also see that the value of the variable a_string that was printed at # # has not changed.

4. Variable life cycle

One notable point is that variables are not only living within a namespace, they all have their own life cycle, see the following example:

>>> def foo (): ...     x = 1>>> foo () >>> print x # 1Traceback (most recent call last):  .... Nameerror:name ' x ' is not defined

#1处发生的错误不仅仅是因为作用域规则导致的 (although this is the cause of the nameerror error) it is also related to the mechanism of function invocation implementations in Python and many other programming languages. At this point in the execution time, there is no valid syntax that allows us to get the value of the variable x because it does not exist at this time! The namespace of the function Foo starts with the function call and ends and destroys.

5. Function parameters

Python allows us to pass arguments to the function, and the arguments become local variables that exist inside the function.

>>> def foo (x): ...     Print locals () >>> foo (1) {' X ': 1}

There are many ways to define and pass parameters in Python, and the full version can view the official Python documentation. Here's a brief explanation: the function parameter can be a required positional parameter or an optional named, default parameter.

>>> def foo (x, y=0): # 1     ... Return x-y>>> foo (3, 1) # 22>>> foo (3) # 33>>> foo () # 4Traceback (most recent call last): 
   ... Typeerror:foo () takes at least 1 argument (0 given) >>> foo (Y=1, x=3) # 52

In the # # Place we define the function foo, which has a positional parameter x and a named parameter Y. We can call the function in the usual way at # #, although there is a named parameter, but the parameter can still be passed to the function through the position. When calling a function, we can also completely ignore the name parameter y as shown in # # #. If the named parameter does not receive any value, Python automatically uses the declared default value, which is 0. It is important to note that we cannot omit the first positional parameter x, otherwise the error will occur as shown in # #.
It's neat and clear at the moment, but it may be a little confusing next. Python supports named arguments when a function is called (personally, it should be a named argument). Look at the function call in the # # #, we pass the two named arguments, this time because of the name identification, the order of parameter passing will not care.
The opposite is true, of course: The second parameter of the function is Y, but we pass the value to it in the way of the position. In the function call Foo (3,1) at # # #, we pass 3 to the first argument and 1 to the second parameter, although the second parameter is a named argument.
Sandy can't afford it, it feels like a big one. This is a simple concept: the parameters of a function can have a name and a position. This means that the definition and invocation of a function are slightly different in understanding. We can pass named arguments (arguments) to functions that define only positional parameters, and vice versa! View official documents If you don't feel enough

6. Nesting functions

Python allows you to create nested functions. This means that we can define functions inside functions and that existing scopes and variable lifetimes still apply.

>>> def outer ():     ... x = 1     ... def inner (): ...         Print x # 1 ...     Inner () # 2...>>> outer () 1

This example is a bit more complicated, but it looks fine. Think about what happened in # #: The Python interpreter needs to find a local variable called X, which will continue to look in the upper scope after the failure, and the upper-level scope is defined in another function. For function outer, the variable x is a local variable, but as previously mentioned, the function inner can access the enclosing scope (at least Read and modify). At # # #, we call the function inner, and it's very important that inner is just a variable name that follows the Python variable parsing rules, and the Python interpreter takes precedence over the variable name inner in the scope of the outer to find the matching variable.

7. function is the first class object in the Python world

Obviously, in Python, functions are objects as well as everything else. (should sing loudly here) Ah! The function that contains the variable, you are not so special!

>>> issubclass (int, object) # All objects in Python inherit from a common baseclasstrue>>> def foo ():. .     pass>>> foo.__class__ # 1<type ' function ' >>>> issubclass (foo.__class__, Object) True

You may never have thought that the function you defined would actually have properties. There is no way, the function in Python is the object, and other things, perhaps this description will be too academic school too official point: in Python, the function is just a few ordinary values and other values of a hair. That means you grams a function like a parameter passed to another function, or from a function that returns a function! If you have never thought of it that way, take a look at the following example:

>>> def add (x, y): ...     return x + y>>> def sub (x, y): ...     return x-y>>> def apply (func, x, y): # 1     ... return func (x, y) # 2>>> apply (Add, 2, 1) # 33>>> Apply (Sub, 2, 1) 1

This example should not be very strange to you. The add and sub are very common two Python functions that accept two values and return a computed result value. In the # # you can see that the variable that is ready to receive a function is just a normal variable, like any other variable. In # # # We call the function that comes in: "() represents the operation of the call and invokes the value that the variable contains. You can also see that the transfer function has no special syntax in the # # place. "The name of the function is just a table identifier that is the same as other variables.
You may have seen this behavior: "Python uses frequently used operations into functions as parameters, such as by passing a function to the key parameter of the built-in sort function to customize the collation." The function as the return value back to the case:

>>> def outer ():     ... def inner (): ...         Print "Inside inner"     ... return inner # 1...>>> foo = outer () #2 >>> foo<function inner at 0x...>>>> foo () Inside in Ner

This example may seem even more strange. In # # I return the variable inner that happens to be the function identifier as the return value. There is no special syntax: "Return the function inner, otherwise it will not be called at all." "Remember the life cycle of variables?" Every time the function outer is called, the function inner is redefined, and if it is not returned as a variable, it will cease to exist after each execution.
At # # # We capture the return value-function inner, and put it in a new variable foo. We can see that when the variable foo is evaluated, it does contain the function inner, and we can call him. It may seem strange for the first time, but it's not difficult to understand. Hold on, because the strange transition is coming soon (Hey Hei hei, I smile is not wretched!) )

8. Closures

Let's not rush to define what a closure is, let's look at a piece of code and simply tweak the previous example:

>>> def outer ():     ... x = 1     ... def inner (): ...         Print x # 1 ...     return inner>>> foo = outer () >>> foo.func_closure (<cell at 0x ...: int object at 0X...>)

In the previous example we learned that inner as a function was returned by outer, saved in a variable foo, and we were able to invoke Foo () on it. But will it run normally? Let's take a look at the scope rules first.
Everything is working under Python's scope rules: "x is a local variable in the function outer. When the function inner prints x in the # #, the Python interpreter looks for the corresponding variable inside the inner, which of course cannot be found, so it goes to the enclosing scope and finds a match.
But from the life cycle of variables, how to understand it? Our variable x is a local variable of the function outer, which means that it only exists when the function outer is running. According to our known Python run mode, we cannot continue to invoke the function inner after the function outer returns, and when the function inner is called, the variable x is no longer present and a run-time error may occur.
Never thought, returned function inner actually can work normally. Python supports a feature called a function closure, which is, by definition, a function nested within a non-global scope to remember the enclosing namespace it is in when it is defined. This can be concluded by looking at the Func_closure property of the function, which contains the value within the enclosing scope (which will only contain the captured value, such as x, if there are other values defined in the outer, the enclosing scope is not)
Remember that every time the function outer is called, the function inner is redefined. Now the value of the variable x does not change, so every time the returned function inner will be the same logic, if we change it a little bit?

>>> def outer (x): ...     def inner (): ...         Print x # 1 ...     return inner>>> print1 = outer (1) >>> Print2 = outer (2) >>> print1 () 1>>> Print2 () 2

From this example you can see that closures-closed scopes remembered by functions-can be used to create custom functions, which are essentially hard-coded parameters. In fact we are not passing parameters 1 or 2 to the function inner, we are actually creating a variety of custom versions that can print various numbers.
A closure is a very powerful feature, and in some ways you might think of it as an object-oriented technique: outer is like a constructor for inner, and X is like a private variable. There are also many ways to use closures: if you are familiar with the parameters of Python's built-in sorting method key, you might have written a lambda method based on the second element rather than the first one when sorting a list of lists. Now you might as well write a Itemgetter method that receives an index value to return a perfect function, passed to the parameter key of the sort function.
However, we are not going to do so low with closures (⊙o⊙) ...! Instead, let's have a great time and write a tall decorator!

9. Decorative Device

An adorner is actually a closure, taking a function as an argument and returning an alternative function. We take a step-by-step look at the complex:

>>> def outer (some_func): ...     def inner (): ...         Print "Before Some_func"         ... ret = Some_func () # 1         ... return ret + 1     ... return inner>>> def foo (): ...     return 1>>> decorated = outer (foo) # 2>>> decorated () before SOME_FUNC2

Take a closer look at the example of the adorner above. We define a function outer, which has only one some_func parameter, in which we define a nested function inner. Inner prints a string of strings and then calls Some_func to get its return value in # # #. The value of Some_func may be different for each invocation of outer, but we will call it regardless of the some_func. Finally, inner returns the value of Some_func () + 1-we can see the printed string as well as the return value of 2 by calling the function stored in the variable decorated at # #, rather than the return value 1 that is expected to be called by the function foo.
We can assume that the variable decorated is a decorative version of the function foo, a reinforced version. In fact, if you're going to write a useful decorator, we might want to replace the original function foo with a decorative version, so we'll always get our "enhanced" foo. To achieve this, there is no need to learn the new syntax, simply assign the variable foo to the line:

>>> foo = outer (foo) >>> foo # doctest: +ellipsis<function inner at 0x...>

Now, any call is not involved in the original function foo, will get the new decorated version of Foo, and now we still write a useful adorner.
Imagine that we have a library that can provide objects of similar coordinates, maybe they are just coordinates of X and Y. Unfortunately, these coordinate objects do not support mathematical operators, and we cannot modify the source code, so we cannot join the support of the operators directly. We're going to do a series of math operations, so we want to be able to do the proper addition and subtraction of two coordinate objects, which can easily be written:

>>> class Coordinate (object): ...     def __init__ (self, x, y): ...         self.x = x         ... Self.y = y ...     def __repr__ (self): ...         Return "Coord:" + str (self.__dict__) >>> def add (A, B): ...     return coordinate (a.x + b.x, A.Y + b.y) >>> def sub (A, b): ...     return coordinate (a.x-b.x, a.y-b.y) >>> one = coordinate (+) >>> one = coordinate (+) >& Gt;> Add (one, both) Coord: {' y ': +, ' x ': 400}

If unfortunately our add-subtract function also requires some boundary check behavior, what should we do? Well, you can only add and subtract positive coordinate objects, and any returned value should be a positive coordinate. So now the expectation is this:

>>> one = coordinate (+) >>> double = coordinate (+) >>> three = coordinate (-100,-100) & Gt;>> Sub (one, both) Coord: {' Y ': 0, ' x ': -200}>>> Add (one, three) Coord: {' y ': +, ' x ': 0}

We expect that without changing the coordinate object one, the other, the three, the value of minus the number of the other is {x:0, Y:0},one plus the three value is {x:100, y:200}. Instead of adding parameters and return value bounds checking logic to each method, let's write a boundary check decorator!

>>> def Wrapper (func): ...     Def Checker (A, B): # 1         ... If a.x < 0 or a.y < 0:             ... A = Coordinate (a.x if a.x > 0 Else 0, a.y if a.y > 0 else 0)         ... If b.x < 0 or b.y < 0:             ... b = Coordinate (b.x if b.x > 0 Else 0, b.y if b.y > 0 else 0)         ... ret = func (A, b)         ... If Ret.x < 0 or Ret.y < 0:             ... ret = coordinate (ret.x if ret.x > 0 Else 0, ret.y if ret.y > 0 else 0)         ... return ret ...     return checker>>> add = wrapper (add) >>> sub = wrapper (sub) >>> Sub (one, both) Coord: {' Y ': 0, ' x ': 0}>>> Add (one, three) Coord: {' y ': $, ' x ': 100}

IDE Demo

#!/usr/bin/env python#-*-coding:utf-8-*-class Coordinate (object): Def __init__ (self, x, y): self.x = x Self.y = y def __repr__ (self): return "Coord:" + str (self.__dict__) def add (A, B): return coordinate (a.x + b . x, A.y + b.y) def sub (A, B): Return coordinate (a.x-b.x, a.y-b.y) one = Coordinate (300, 200 ) Print (Add (one, both)) #Coord: {' y ': 400}print, ' x ': Coordinate ("------------------------------------") one = (100,  (+)-coordinate (+) three = coordinate ( -100, -100) print (sub (one, both)) #Coord: {' Y ': 0, ' x ': -200}print (add one, Three) #Coord: {' y ': +, ' x ': 0}print ("------------------------------------") def wrapper (func): Def checker (A, b): #        1 if a.x < 0 or a.y < 0:a = Coordinate (a.x if a.x > 0 Else 0, a.y if a.y > 0 Else 0) If b.x < 0 or b.y < 0:b = Coordinate (b.x if b.x > 0 Else 0, b.y if b.y > 0 else 0) ret = f UNC (A, B) if Ret.x <0 or Ret.y < 0:ret = Coordinate (ret.x if ret.x > 0 Else 0, ret.y if ret.y > 0 Else 0) return R ET return checkeradd = wrapper (add) Sub = wrapper (sub) print (Sub (one, both)) #Coord: {' Y ': 0, ' x ': 0}print (Add (one, three)) #Coord: {' y ': $, ' x ': 100}

Results:

Coord: {' x ': +, ' y ': $------------------------------------Coord: {' x ': -200, ' y ': 0}coord: {' x ': 0, ' y ': $-------- ----------------------------Coord: {' x ': 0, ' y ': 0}coord: {' x ': +, ' y ': 200}

This decorator can work like a previous decorator example, returning a modified function, but in this case it can do some very useful checking and formatting of the input and return values of the function, replacing the negative x and Y with 0.
Obviously, in this way, our code becomes more concise: Isolate the logic of the boundary check into a separate method, and then apply the adorner wrapper to where we need to check. Another way to do this is by calling the method of bounds checking before the start of the calculation method and the return value. However, it is not possible to use adorners to allow us to achieve the goal of coordinate bounds checking with minimal amount of code. In fact, if we are decorating our own definition of the method, we can make the adorner application more force lattice.

10. Apply an adorner to a function using the @ identifier

Python2.4 supports the use of identifiers @ to apply adorners to functions, just precede the definition of the function with the name of the @ and adorner. In the example in the previous section, we replaced the original method with a decorative method:

>>> add = wrapper (add)

This way, any method can be packaged at any time. But if we customize a method, we can decorate with @:

>>> @wrapper ... def add (A, B): ...     return coordinate (a.x + b.x, A.Y + b.y)

It should be understood that this approach and the previous simple packaging method to replace the original method is a hair, python just added some syntax sugar to make the behavior of the decoration more directly clear and elegant point.

#!/usr/bin/env python#-*-coding:utf-8-*-def Login (func):  #传入参数func = TV    def inner (ARG):        print ("Passed User verifcation ... ")        func (ARG)    return innerdef TV (name):    print (" Welcome [%s] to TV page "% (name)) TV = Login (TV) TV (("Amu"))

Results:

Passed user verifcation ... Welcome [AMU] to TV page

Use the identifier @ To apply the adorner to the function

#!/usr/bin/env python#-*-coding:utf-8-*-def Login (func):  #传入参数func = TV    def inner (ARG):        print ("Passed User verifcation ... ")        func (ARG)    return inner@login    #装饰器 use the identifier @ To apply the adorner to the function def TV (name):    Print (" Welcome [%s] to TV page "% (name)" #tv = login (TV) TV (("Amu"))

Results:

Passed user verifcation ... Welcome [AMU] to TV page
*args and **kwargs

We have completed a useful adorner, but for hard coding it can only be applied to a specific class of methods, which receives two parameters and passes it to the function of the closure capture. What if we want to implement an adorner that can be applied to any method? For example, if we want to implement a counter-like adorner that can be applied to any method, there is no need to change any logic of the original method. This means that the adorner can accept a function that has any signature as its own adornment method, while being able to invoke the decorated method with the parameters passed to it.
It is a coincidence that Python has the syntax to support this feature. You can read the Python Tutorial for more details. The use of * when defining a function means that parameters passed by location will be placed in a variable with a * prefix, so:

>>> def One (*args): ...     Print args # 1>>> one () >>> one (1, 2, 3) (1, 2, 3) >>> def (x, Y, *args): # 2 ...     Print x, y, args>>> (' A ', ' B ', ' C ') a B (' C ',)

The first function one simply says that any pass-through positional parameters are all printed out, and you can see that in code # # We just refer to the variable args within the function, and *args just used the function definition to indicate that positional parameters should be stored in the variable args. Python allows us to set some parameters and capture all the remaining non-captured positional parameters through args, as shown in Listing #.
The * operator can also be used when a function is called. The meaning is basically the same. When a function is called, a variable with a * flag means that the contents of the variable need to be extracted and used as positional parameters. Similarly, let's look at an example:

>>> def add (x, y): ... return x + y>>> LST = [1,2]>>> Add (lst[0], lst[1]) # 13>>> Add (* LST) # 23

The code at the #1处的代码和 # # is actually the same thing, and what Python does for us can actually be done manually. This is not a bad thing, *args either means that when the calling method is large, additional parameters can be obtained from an iterative list, or the method is defined to accept arbitrary positional parameters.
The next mention of the * * will be slightly more complex, * * represents the key value pairs of the parameter dictionary, and * represents the meaning of the same, is very simple right:

>>> def foo (**kwargs): ...     Print kwargs>>> foo () {}>>> foo (x=1, y=2) {' Y ': 2, ' X ': 1}

When we define a function, we can use **kwargs to indicate that all the non-captured keyword parameters should be stored in the Kwargs dictionary. As previously mentioned, Argshe Kwargs is not part of the Python syntax, but when defining a function, using such variable names is an unwritten convention. As with *, we can also use * * when defining or invoking a function.

>>> DCT = {' x ': 1, ' Y ': 2}>>> def bar (x, y):     .... return x + y>>> bar (**DCT) 3
12. More Versatile adorners

With this new skill, we can casually write an adorner that can record the parameters passed to the function. Let's start with an example of simply outputting the log to the interface:

>>> def Logger (func): ...     def inner (*args, **kwargs): #1 ...         Print "Arguments were:%s,%s"% (args, Kwargs)         ... return func (*args, **kwargs) #2 ...     return inner

Notice our function inner, which can accept any number and type of arguments and pass them to the wrapped method, which allows us to decorate any method with this adorner.

>>> @logger ... def foo1 (x, Y=1): ...     return x * y>>> @logger ... def foo2 ():     ... Return 2>>> foo1 (5, 4) Arguments were: (5, 4), {}20>>> foo1 (1) Arguments were: (1,), {}1>>> Foo2 ( ) Arguments were: (), {}2

Call whatever method we define, and the corresponding log will print to the Output window, as we expected.

12 easy steps to handle Python adorners

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.