<title>12-Step Easy python decorator-Pinterest</title>
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 def
defined by a keyword, a function name, and an optional parameter list. return
returns a value by 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 look at 本地作用域
and 全局作用域
what's different:
>>> 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 inside of the function. We can see that the function foo
has its own independent 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 foo
implementation of the function so that it prints the variables in the global scope, it 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 a_string
the variable, which 本地作用域
is not found in 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. Inside the function, we actually 新创建
have a local variable with 隐藏
the same name 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 that was printed at # # a_string
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处发生的错误不仅仅是因为 作用域规则
Cause (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 x
the value of the variable because it does not exist at this time! foo
the namespace of the function begins with the function call and ends and is destroyed.
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 parameters of the function can be either mandatory 位置参数
or 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 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, y
we can also completely ignore the named parameter as shown in # #. If the named parameter does not receive any value, Python automatically uses the declared default value 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 at # # foo(3,1)
, we pass the first argument to the second parameter 3
1
, although the second parameter is a named parameter.
Sandy can't afford to have a good freshman paragraph to clarify such a simple concept: function parameters can have 名称
and 位置
. 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 x
local variable called, and the lookup fails to continue looking in the upper scope, which is defined in another function. For outer
a function, x
a variable is a local variable, but as mentioned earlier, the function inner
can access the enclosing scope (at least Read and modify). At the point where we call the function, it is very important that it is inner
inner
just a variable name that follows the rules of the Python variable parsing, and the Python interpreter takes precedence over outer
the variable name in the scope to inner
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. add
and sub
is a very common two Python function, accepts two values, returns 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 action 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 parameters of the built-in sort function to key
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 inner
This example may seem even more strange. In # # I return the variable that happens to be a function identifier inner
as a 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?" Each 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.
In # # # We capture the return value-function inner
and put it in a new variable foo
. We can see that when a variable foo
is evaluated, it does contain a function inner
, and we can call it. 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
returned as a function outer
, saved in a variable foo
, and we were able to call it foo ()
. 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 finds the corresponding variable inside the inner
, which of course cannot be found, so it is then searched in the enclosing scope, And a match will be found.
But how do you understand it from the life cycle of a variable? 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, when the function inner
is called, the variable x
no longer exists, a run-time error may occur.
never thought that the function returned inner
would work correctly. Python supports a feature called function Closure
, which, in the words of the word, is that a function nested within a non-global scope
can remember the enclosing namespace it was in when it was defined. This can be concluded by looking at the func_closure
property of the function, which contains the values inside the enclosing scope (only the values that are captured, such as x
, if the outer
It also defines other values, which are not available in the enclosing scope
Remember that the function inner
is redefined every time the function outer
is called. Now the value of the variable x
does not change, so the function inner
returned every time is 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 闭包
-the enclosing scope remembered by the function-can be used to create a custom function, essentially one 硬编码的参数
. In fact we are not passing arguments 1
or 2
giving functions 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
like inner
a constructor for a service, x
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 methods key
, you might have written a lambda
method to sort a list of lists based on the second element instead of the first one. Now you might as well write a itemgetter
method that receives an index value to return a perfect function, passed to the parameter of the sort function key
.
However, we are not going to do so low with closures (⊙o⊙) ...! 让我们再爽一次
on the contrary, write a tall on 装饰器
!
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
A string is printed and then called, and the some_func
return value is obtained at # # #. The outer
value may be different at each call some_func
, but whatever some_func
it is, we'll call it. Finally, inner
some_func() + 1
The returned value-we decorated
can see the printed string as well as the return value by calling the function stored in the variable at 2
# #, rather than foo
the return value expected from the calling function 1
.
We can assume that a variable decorated
is foo
a decorative version of a function, a reinforcing version. In fact, if we're going to write a useful decorator, we might want to replace the original function with a decorative version foo
, so we'll always get our "enhanced version foo
." To achieve this, there is no need to learn a new syntax, simply assign a value to the variable foo
on 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 a new decorative version 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(100, 200)>>> two = Coordinate(300, 200)>>> add(one, two)Coord: {‘y‘: 400, ‘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(100, 200)>>> two = Coordinate(300, 200)>>> three = Coordinate(-100, -100)>>> sub(one, two)Coord: {‘y‘: 0, ‘x‘: -200}>>> add(one, three)Coord: {‘y‘: 100, ‘x‘: 0}
We expect the value to be subtracted if the coordinate object is not changed one, two, three
one
two
{x: 0, y: 0}
, one
plus three
the 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 ': +}
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 values with the ones x
y
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. Use
@Identifiers apply adorners to functions
Python2.4 supports the use of identifiers to @
apply adorners to functions, simply by adding @
and decorating the name of the adorner before the function is defined. 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 use it @
to decorate:
>>> @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.
*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. When a function is defined, it *
means that the parameters passed by the position will be placed in the variable with *
the prefix, so:
>>> 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 simply says that one
any pass-through positional parameters are all printed out, and you can see that in code # # We simply refer to variables within the function args
, simply *args
using the function definition to indicate that positional parameters should be stored in the variable args
. Python allows us to set some parameters and args
capture all the remaining non-captured positional parameters, as shown in # #.
*
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 either, it means that the *args
extra arguments can be taken from an iterative list when the calling method is large, or that the method can accept arbitrary positional parameters when defining the method.
The next mention **
will be slightly more complex, **
representing the key-value pairs of the dining-house dictionary, and *
the meaning of the representative of the same, and 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 it **kwargs
to indicate that all the non-captured keyword parameters should be stored in kwargs
the dictionary. As previously mentioned, args
he kwargs
is not part of the Python syntax, but when defining a function, it is an unwritten convention to use such variable names. *
as well, we can also use it 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
Note Our function inner
, which can accept any number and type of parameters 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.
Original address: Understanding Python decorators in the easy steps!
From for notes (Wiz)
12-Step Easy python decorator-Pinterest