Deep understanding of the concept and meaning of Python adorners

Source: Internet
Author: User
Tags python decorator
Perhaps you have used a decorator, it is very simple to use, but difficult to understand (in fact, it is very easy to understand), to understand the adorner, you need to understand the concept of functional programming, Python function definition and function call syntax rules, etc., although I can not make the adorner easy, But I hope that the following steps can help you understand what the adorner is. Assuming you have the most basic Python knowledge, what this article describes may be of great help to those who are often exposed to python at work. Let's step through the Python decorator:

1. function (Functions)

In Python, a function is created with the DEF keyword followed by a function name and an optional parameter table column, and you can specify the return value with the keyword return. Let's create and invoke one of the simplest functions:

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

The function body of the function (which would be a multiline statement in Python) is mandatory and is indicated by indentation. We can call the function by adding a double parenthesis after the function name.

2. Scope (SCOPE)

In Python, each function creates a scope. Pythonistas may also be called functions that have their own namespaces (namespace). This means that when a variable name is encountered in the function body, Python is first looked up in the function's namespace, and Python contains some functions that let us look at the namespace. Let's write a simple function to explore the differences between local and global scopes.

>>> a_string = "This is a global variable" >>> def foo ():     ... Print locals () >>> print globals () # Doctest: +ellipsis{..., ' a_strin ': ' This ia a global variable '}>>> f OO () # 2{}

The built-in Globals function returns a Dictionary object that contains all Python-aware variable names (I have omitted some python-created variables for clarity). I called the function foo at # #, which prints out the contents of the local namespace inside the function. As we can see, the Foo function has its own independent namespace, and now it's empty.

3. Variable parsing rule (variable resolution rules)

Of course, this does not mean that we cannot access global variables inside the function. Python's scope rule is that the creation of a variable always creates a new local variable, but the access to the variable (including the modification) finds the local scope and then looks for a match along the nearest scope. So if we modify the Foo function to let it print the global variable, the result will be as we would expect:

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

In the # # place, Python looks for a local variable in the function, but does not find it, and then finds a variable with the same name in the global variable.

On the other hand, if we try to assign a value to the global variable in a function, the result will be less than we would like:

>>> 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 '

As we have seen, global variables can be accessed (which can even be changed if they are mutable types), but cannot be assigned (by default). Inside the function we actually create a new local variable that has the same name as the global variable, which overrides the global variable. We can find out that it already has an entry by printing the local namespace inside the Foo function, and we can see that the value of the variable a_string is not changed at all by the output from the outside of the function.

4. Life cycle of variables (Variable lifetime)

Also note that variables not only "live" in a namespace, they also have a life cycle. Consider the following code:

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

Not only is there a problem with scope rules (although this is the reason for Nameerror), but also because of the implementation of function calls in Python and many other languages. Here, we don't have any available syntax to get the value of the variable x-literally nonexistent. Each time the Foo function is called, its namespace is rebuilt and destroyed at the end of the function.

5. Parameters of the functions (function parameters)

Python allows us to pass arguments to the function. The parameter name becomes the local variable for the function.

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

Python has many different methods for defining and passing function parameters. For more in-depth understanding, refer to the Python documentation on defining functions. Here I show a simple version: a function parameter can be either a mandatory positional parameter (positional parameters) or a named parameter, and the default value of the parameter is optional.

>>> 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 a function with a positional parameter x and a named parameter Y. As we can see, at # # we could call the function by passing a normal value, even if a parameter (the translator's note: This refers to the parameter y) is defined in the function definition as a named parameter. As we can see in the # #, we can even call the function without passing any value for the named parameter--if the Foo function does not receive a value passed to the named parameter y, Python will call the function with the default value we declared. Of course, we can't miss the first (mandatory, fixed-position) parameter-#4以一个异常描述了这种错误.

are clear and straightforward, aren't they? The following is a bit confusing.--python also supports named arguments at function calls, not just when the function is defined. Look at the # # #, here we call the function with two named arguments, although the function is defined with a name and a positional parameter. Because our parameters have names, the position of the parameters we pass will not have any effect. The opposite is certainly true. One parameter of our function is defined as a named parameter but we pass the parameter by position--#4处的调用foo (3, 1) passes a 3 as the first parameter to our ordered parameter x and passes the second argument (integer 1) to the second parameter, although it is defined as a named parameter.

Whoo! It's like using a lot of words to describe a very simple concept: a function parameter can have a name or position.

6. Inline function (Nested functions)

Python allows the creation of nested functions, which means that we can declare functions within a function and all scopes and claim-cycle rules also apply.

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

This looks slightly more complex, but its behavior is still fairly straightforward and easy to understand. Think about what happened in # # # # # # # # # # # # # # # # # # # # # #--python Looking for a local variable named X, failed, then searched in the nearest outer scope, which is another function! The variable x is the local variable of the function outer, but as mentioned earlier, the inner function has access to the external scope (at least Read and modify permissions). We called the inner function at the # # # place. Remember that inner is also just a variable name, and it follows the Python variable lookup rule--python first finds it in the scope of outer, and finds a local variable named inner.

7. function is first class citizen (Functions is first objects in Python)

In Python, this is common sense, and the function is the same object as anything else. Well, the function contains the variable, it's not so special!

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

You probably never thought that a function has properties, but in Python, like anything else, a function is an object. (If you find this confusing, please wait and know that you understand that in Python, like anything else, class is an Object!) It may be that this makes Python somewhat "academic"--just a regular value in Python like any other value. This means that you can pass the function as a parameter to the function or return the function as a return value in the function! If you have never considered such a thing, consider the following legal Python code:

>>> 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 may not be too strange for you.--add and sub are standard Python functions that accept two values and return a computed result. In # # # you can see that variables accept a function like any other normal variable. In # # # We call the function that passed in to apply--in Python, the double parenthesis is the calling operator, and the value that the variable name contains is called. You can see that in Python there is no special syntax for passing a function as a value--the function name is just a variable label like any other variable.

You've probably seen this before.--python functions as parameters are often seen in operations such as defining sorted built-in functions by providing a function for the key parameter. But what happens when a function is returned as a return value? Please consider:

>>> def outer ():     ... def inner (): ...         Print "Inside inner"     ... return inner # 1...>>> foo = outer () #2 >>> foo # doctest:+ellipsis<function inner at 0x...>>> ;> foo () Inside inner

This may seem a little strange at first glance. In # # I returned the variable inner, which happens to be a function tag. There is no special syntax here-our function returns the inner function (calling the outer () function does not produce visible execution). Remember the life cycle of variables? Whenever the outer function is called, the inner function is redefined once, but if the inner function is not returned (outer) then the outer will no longer be present when the scope of the inner is exceeded.

We can get to the return value at # #, it is our inner function, it is stored in a new variable foo. We can see that if we calculate foo, it really contains the inner function that we can do by using the call operator (double brackets, remember?). ) to invoke it. This may seem a little weird, but so far there's nothing to understand, is it? Hold on, because the next thing is going to be weird.

8. Closure package (Closures)

Let's start with another code example, not from the definition. What if we were to make a slight change to the previous example?

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

From the previous example, we see that inner is a function returned by outer, stored in a variable named Foo, which we can call through Foo (). But does it work? Let's take a look at the scope rules first.

Everything runs according to Python's scope rules--x is a local variable for the outer function. When inner prints x in # #, Python looks for a local variable in the inner, and it finds it in the outer scope, the outer function.

But what about this from the perspective of the variable life cycle? The variable x is the local variable of the function outer, which means it exists only when the outer function is running. It is only when outer returns that we can call inner, so according to our model of how Python works, when we call inner, X is no longer present, and a run-time error may occur.

The fact is not consistent with our expectations, and the returned inner function does work correctly. Python supports a feature called closure (function closures), which means that inner functions defined in non-global scopes remember what their outer scopes look like when defined. This can be viewed by looking at the Func_closure property of the inner function, which contains the variables in the outer scope.

Keep in mind that the inner function is redefined every time the outer function is invoked. The current value of x does not change, so each inner function we get has the same behavior as the other inner functions, but what if we make a little change?

>>> 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 the fact that the closures--function remembers their outer scopes--it can be used to build custom functions that inherently have a hard-coded parameter. We did not pass the number 1 or 2 to our inner function but built a custom version that "remembers" the number that it should print.

Closures is a powerful technique-you even think that in some ways it's a bit like object-oriented technology: outer is a inner constructor, and X plays a role like a private member variable. It has many functions, and if you are familiar with the key parameter of the Python sorted function, you may have written a lambda function to sort some column list by the second rather than the first item. Perhaps you can now write a itemgetter function that receives an index for retrieval and returns a function that is suitable for passing to the key parameter.

But let's not use closures to do any nightmare thing! Instead, let's start from scratch to write a decorator!

9. Adorner (decorators)

A decorator is simply a closure with a function as a parameter and returns a replacement function. We will start from the simple beginning to the writing of useful decorators.

>>> 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

Please take a closer look at our decorator example. We define a function named outer that accepts a single parameter, Some_func. Inside outer we define a nested function named inner. The inner function prints a string and then calls Some_func, which caches its return value in # #. The value of Some_func may be different every time outer is called, but whatever it is, we will call it. Finally, inner returns the return value of Some_func plus 1, and we can see that when we call the return function stored in decorated, we get the output text and a return value of 2 instead of the original value 1 that we expect the call Foo to produce.

We can say that the decorated variable is a "decorative" version of Foo--something made up of Foo plus some stuff. In fact, if we write a useful decorator, we might want to replace Foo with a decorated version, so we can get the "add something" version of Foo. We can do this without learning any new syntax--re-assigning variables that contain our functions:

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

Now any call to Foo () won't get the original Foo, but will get our decorated version! Have you grasped some decorator thoughts?

10. The grammatical sugar--@ symbol of the adorner (the @ symbol applies a decorator to a function)

Python 2.4 implements the wrapper for a function by adding an @ symbol before the function definition. In the above code example, we have wrapped a function to replace the variable containing the function to implement the wrapper.

>>> add = wrapper (add)

This pattern can be used to wrap any function at any time, but if you define a function, we can wrap it with the @ sign as in the following example:

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

Note that this is no different than replacing the original variable with the return value of the wrapper function, and Python just adds some syntactic sugars (syntactic sugar) to make it look a little more obvious.

11, *args and **kwargs

We've written a useful decorator, but it's hard-coded, it's only for a certain kind of function--a function with two parameters. The checker function inside our function accepts two parameters and then continues to close the function in the argument. What if we want a decorator that can wrap any type of function? Let's implement a wrapper that adds a count of calls to each wrapper function without changing the wrapper function. This means that the decorator needs to accept any function to be wrapped and pass any arguments passed to it to the wrapped function to invoke it (the wrapped function).

This is a common situation, so Python provides syntax support for this feature. Be sure to read the Python tutorial to learn more, but using the * operator when defining a function means that any additional positional arguments passed to the function will eventually be preceded by a *. 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 prints out any positional parameters that are passed to it (if any). As you see in the # # place, inside the function we just quoted the args variable--*args just to indicate that the positional parameter in the function definition should be stored in the variable args. Python also allows us to specify some variables and capture any other parameters in the args variable, as shown in # #.

The * operator can also be used in function calls, when it has a similar meaning. When a function is called with a variable that is preceded by a * as a parameter, the content of the variable needs to be parsed and then used as the positional parameter. Once again, illustrate by example:

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

#1处的代码抽取出了和 the same parameters on # #--Python automatically parses the parameters for us at # # #, and we can parse them out like we did in the # # place. This looks good, *args both means that when the calling function is extracting positional arguments from a iterable, it also means that when a function is defined, it accepts any additional positional variables.

When we introduce * *, things become more complicated, as with * denotes iterables and positional parameters, * * denotes dictionaries & key/value pairs. It's simple, isn't it?

>>> 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 keyword variables that are not captured should be stored in a dictionary named Kwargs. Both the args in the previous example and the Kwargs in this example are not part of the Python syntax, but it is customary to use these two as variable names when defining a function. Just like, we can use \* when a function is called.

>>> DCT = {' x ': 1, ' Y ': 2}>>> def bar (x, y):     .... Rturn x + y>>> Bar (**DCT) 3

12. More versatile decorators (more generic decorators)

With our new "weapons" we can write a decorator that is used to "record" the function. For the sake of simplicity, we print it on the stdout:

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

Notice that the inner function takes any number of parameters of any type at # #, and then passes them to the wrapped function at # # #. This allows us to wrap or decorate any function.

>>> @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

A call to a function produces a "Logging" output line, and it also outputs a function return value as we expect.

If you have been following the last instance, congratulations, you have understood the 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.