Step 12 teaches you how to understand Python decorators and Step 12 understands python decorations
The following steps allow you to understand what the decorator is. Assuming that you have the most basic Python knowledge, the things described in this article may be of great help to those who often access Python at work.
1. Functions)
In Python, functions are created using the def keyword followed by a function name and an optional parameter table column. You can use the return keyword to specify the return value. Let's create and call the simplest function:
>>> def foo():... return 1>>> foo()1
The function body of this function (in Python it will be a multi-line statement) is mandatory and indicated by indentation. We can call a function by adding double parentheses after the function name.
2. Scope)
In Python, each function creates a scope. Pythonistas may also be called a function with its own namespace ). This means that when a variable name is encountered in the function body, Python first searches for the namespace of the function. Python contains some functions for us to view the namespace. Let's write a simple function to find out the difference 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'}>>> foo() # 2{}
The built-in globals function returns a dictionary object that contains all the variable names known to Python (for clarity, I have ignored some automatically created Python variables ). At #2, I called the function foo, which prints the content in the local namespace inside the function. As we can see, the foo function has its own independent namespace. Now it is empty.
3. variable resolution rules (variable resolution rules)
Of course, this does not mean that we cannot access global variables within the function. In Python, the scope rule is that a new local variable is always created when a variable is created, but the access to the variable (including modification) First searches for the local scope and then searches for matching based on the nearest scope. Therefore, if we modify the foo function to print the global variable, the result will be as expected:
>>> a_string = "This is global variable">>> def foo():... print a_string # 1>>> foo()This is a global variable
At #1, Python looks for a local variable in the function but does not find it. Then, it 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 the function, the result will not be as expected:
>>> 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 can see, global variables can be accessed (if they are of a mutable type, they can even be changed), but (by default) cannot be assigned values. In the #1 part of the function, we actually created a new local variable, which has the same name as the global variable and overwrites the global variable. We can print the local namespace inside the foo function to find that it already has an entry. We can see the output result at #2 outside the function, the value of the variable a_string is not changed at all.
4. Variable Life Cycle)
You should also note that variables not only "live" in a namespace, but 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
At #1, not only does the scope rule cause a problem (although this is the cause of NameError), but also is implemented by function calls in Python and many other languages. Here, we do not have any available syntax to obtain the value of variable x-literally, it does not exist. Every time the foo function is called, its namespace is rebuilt and destroyed when the function ends.
5. Function parameters)
Python allows us to pass parameters to functions. The parameter name is the local variable of the function.
>>> def foo(x):... print locals()>>> foo(1){'x': 1}
Python has many different methods for defining and passing function parameters. For more details, see the Python documentation on defining functions. Here I will show a simple version: function parameters can be either positional parameters or named parameters. 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
At #1, we define a function with a location parameter x and a named Parameter y. As we can see, in section #2, we can call a function by passing common values, even if a parameter is used) A name parameter is defined in the function definition. In section #3, we can call the function without passing any value for the named parameter. If the foo function does not receive the value passed to the named Parameter y, python will call the function with the declared default value 0. Of course, we cannot miss the first (mandatory, fixed location) parameter -- #4 describes this error with an exception.
They are clear and straightforward, aren't they? The following is a bit confusing-Python also supports named parameters for function calling, not just when defining a function. Please refer to section #5. Here we use two named parameters to call the function, although this function is defined by one name and one location parameter. Because our parameter has a name, the location of the parameter we pass will not be affected. The opposite is true, of course. A parameter of our function is defined as a named parameter, but we pass the parameter -- # Call foo (3, 1) at location 4) pass 3 as the first parameter to the sorted parameter x and pass the second parameter (integer 1) to the second parameter, even though it is defined as a named parameter.
Whoo! This is like describing a very simple concept in many words: function parameters can have names or locations.
6. Nested functions)
Python allows the creation of nested functions, which means that the function can be declared within the function, and all the scopes and declaration cycle rules also apply.
>>> def outer():... x = 1... def inner():... print x # 1... inner() # 2...>>> outer()1
This seems a little complicated, but its behavior is still quite straightforward and easy to understand. Think about what happened at #1 -- Python looked for a local variable named x, failed, and then searched in the nearest outer scope. This scope is another function! Variable x is the local variable of the function outer, but as mentioned above, the inner function has access permissions to the external layer scope (at least read and modify permissions ). At #2, we call the inner function. Remember that inner is only a variable name. It also follows the Python Variable Search Rule. Python first searches for it in the outer scope and finds a local variable named inner.
7. Functions are first-class citizens (Functions are first class objects in Python)
In Python, this is a common sense. A function is the same object as anything else. Er, the function contains variables, which are 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 may have never imagined that a function also has attributes, but in Python, a function is an object like anything else. (If you find this confusing, please wait and know that in Python, class is also an object like anything else !) Perhaps this makes Python somewhat "academic"-in Python, just like any other value is a regular value. This means that you can pass functions as parameters to functions or return functions as return values! If you have never considered this, 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, both of which accept two values and return a computed result. At #1, you can see that the variable accepts a function, just like any other common variable. In section #2, we call the apply passed function. In Python, parentheses are call operators and the values contained in variable names are called. In #3, you can see that in Python, passing functions as values does not have any special syntax-function names are just variable labels like any other variables.
You may have seen this behavior before-Python uses functions as parameters and is often seen in operations such as customizing sorted built-in functions by providing a function for the key parameter. But what if I return a function as a return value? Consider:
>>> def outer():... def inner():... print "Inside inner"... return inner # 1...>>> foo = outer() #2>>> foo # doctest:+ELLIPSIS<function inner at 0x...>>>> foo()Inside inner
It seems a bit strange at first glance. At #1, 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 ). Do you still remember the lifecycle of a variable? The inner function is redefined every time the outer function is called. However, if the inner function is not returned by the outer function, the inner will no longer exist when it exceeds the outer scope.
At #2, we can get the return value. It is our inner function, which is stored in a new variable foo. We can see that if we calculate foo, it actually contains the inner function, we can use the call operator (double parentheses, remember ?) To call it. This may seem a bit weird, but so far there is nothing difficult to understand, isn't it? Hold on, because the next thing will be weird.
8. Closure (Closures)
Let's start from another sample code instead of definition. What if we make a slight modification 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 above example, we can see that inner is a function returned by outer and stored in a variable named foo. We can call it through foo. But can it run? Let's first consider the scope rules.
Everything runs according to Python's scope rules -- x is a local variable of the outer function. When the inner prints x at #1, Python looks for a local variable in the inner and does not find it. Then it searches for and finds it in the outer scope, that is, the outer function.
But what will happen from the perspective of variable lifecycle? Variable x is the local variable of the function outer, which means it exists only when the outer function is running. Inner can be called only after outer returns. Therefore, according to our model on how Python works, x no longer exists when we call inner, A running error may occur.
The facts are inconsistent with our expectation. The returned inner function does run normally. Python supports a feature called function closures, which means that inner functions defining non-Global scopes remember what their outer scopes look like when defining them. This can be viewed by viewing the func_closure attribute of the inner function, which contains variables in the outer scope.
Remember that the inner function is redefined every time the outer function is called. At present, the value of x has not changed, so each inner function we get has the same behavior as other inner functions. But what if we change it a bit?
>>> def outer(x):... def inner():... print x # 1... return inner>>> print1 = outer(1)>>> print2 = outer(2)>>> print1()1>>> print2()2
In this example, you can see that closures-the fact that the function remembers their outer scope-can be used to build a UDF with a hard-coded parameter in nature. We didn't pass numbers 1 or 2 to our inner function, but built a custom version that "remembers" It should print numbers.
Closures is a powerful technology-you even think that it is somewhat similar to object-oriented technology in some aspects: outer is an inner constructor, and x plays a role similar to a private member variable. It has many functions. If you are familiar with the key parameters of the Python sorted function, you may have written a lambda function to sort the list of some columns through the second item instead of the first item. Maybe you can write an itemgetter function that receives an index for retrieval and returns a function. This function is suitable for passing to the key parameter.
But let's not use closures to do anything that's a nightmare! Instead, let's write a new decorator from the beginning!
9. Decorators)
A decorator is just a closure with a function as a parameter and returns a replacement function. We will write useful decorators from the very beginning.
>>> 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 our decorator instance. We define a function named outer that accepts a single parameter some_func. In outer, we define a nested function named inner. The inner function prints a string and calls some_func to cache its return value at #1. The value of some_func may be different each time the outer is called, but whatever it is, we will call it. Finally, inner returns the some_func return value plus 1, and we can see that, when we call the return function stored in decorated at #2, we get the output text and a return value 2 instead of the original value 1 we expected to call foo.
We can say that the decorated variable is a "decoration" version of foo, which consists of foo and some other things. In fact, if we write a useful decorator, we may want to replace foo with the decorated version to get the "add something" version of foo. We can do this without learning any new syntax-re-assign values to variables that contain our functions:
>>> foo = outer(foo)>>> foo # doctest: +ELLIPSIS<function inner at 0x...>
Currently, no call to foo () will get the original foo, but the decorated version! Have you understood some decorator ideas?
10. The modifier syntax sugar -- @ symbol (The @ symbol applies a decorator to a function)
Python 2.4 adds a @ symbol before the function definition to encapsulate the function. In the preceding code example, we use a wrapped function to replace the variable containing the function to implement the packaging.
>>> add = wrapper(add)
This mode can be used to wrap any function at any time, but if we define a function, we can use the @ symbol to wrap it like in the following example:
>>> @wrapper... def add(a, b):... return Coordinate(a.x + b.x, a.y + b.y)
Note that this method is no different from replacing the original variable with the return value of the wrapper function. Python only adds some syntactic sugar (syntactic sugar) to make it look more obvious.
11. * args and ** kwargs
We have written a useful decorator, but it is hard-coded. It only applies to specific types of functions-functions with two parameters. The checker function in our function accepts two parameters, and then continues the function in the closure of the parameter. What if we want a decorator that can wrap any type of function? Let's implement a wrapper that adds a count for each call of the encapsulated function without changing the encapsulated function. This means that the decorator needs to accept all functions to be packaged and pass any parameters passed to it to the encapsulated function to call it (the encapsulated function ).
This is common, so Python provides syntax support for this feature. Please be sure to read Python Tutorial for more information, but using the * operator in function definition means that any extra location parameter passed to the function will eventually begin with. Therefore:
>>> def one(*args):... print args # 1>>> one()()>>> one(1, 2, 3)(1, 2, 3)>>> def two(x, y, *args): # 2... print x, y, args>>> two('a', 'b', 'c')a b ('c')
The first function one simply prints out any (if any) location parameter passed to it. As you can see at #1, in the function, we only reference The args variable -- * args only indicates that the location parameter in the function definition should be saved in the args variable. Python also allows us to specify some variables and capture any other parameters in args variables, as shown in #2.
* Operators can also be used in function calls. In this case, they also have similar meanings. When calling a function, there is a variable with * as the leading parameter to indicate that the content of this variable needs to be parsed and then used as the location parameter. The following example is used again:
>>> def add(x, y):... return x + y>>> lst = [1, 2]>>> add(lst[0], lst[1]) # 13>>> add(*lst) # 23
# The code at 1 extracts the same parameters as that at #2 -- Python automatically parses the parameters at #2, we can also parse it as we do at #1. This looks good. * args indicates that when a function is called to extract a location parameter from an iterable, it also means that when defining a function, it accepts any additional location variable.
When we introduce **, things become more complex. Like * indicating iterables and location parameters, ** indicates dictionaries & key/value pairs. It's easy, isn't it?
>>> def foo(**kwargs):... print kwargs>>> foo(){}>>> foo(x=1, y=2){'y': 2, 'x': 1}
When defining a function, we can use ** kwargs to indicate that all uncaptured keyword variables should be stored in a dictionary named kwargs. The args in the preceding example and kwargs in this example are not part of the Python syntax, but it is a Convention to use these two as variable names during function definition. Just like, we can use \ * for function calls \*.
>>> dct = {'x': 1, 'y': 2}>>> def bar(x, y):... rturn x + y>>> bar(**dct)3
12. More general decorators)
With the new "Weapon" we have mastered, we can write a parameter that the decorator uses to "record" the function. For simplicity, we print it on stdout:
>>> def logger(func):... def inner(*args, **kwargs): # 1... print "Arguments were: %s, %s" % (args, kwargs)... return func(*args, **kwargs) # 2... return inner
Note that the inner function at #1 carries any number of parameters of any type, and then passes them to the encapsulated function at #2. This allows us to wrap or describe 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
Calling a function produces a "logging" output line and a function return value as expected.
If you keep following the last instance, congratulations! You have understood the decorators! You can use your new weapon to do better things.
I hope you will like it and it will be helpful for your learning.
Articles you may be interested in:
- Using the Python decorator without calling the parent class Constructor
- Example of using the python decorator
- Python retries decorator example
- Use the python decorator to verify the configuration file example
- An infinite recursion problem caused by python's descriptor and property
- Decorator for Python deep learning
- Decorator usage in Python
- Detailed description of function programming for Python decorators
- A detailed tutorial on the decorator, closure, and functools in Python
- Multiple decorators in Python