程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

Learn Python decorator easily in an article

編輯:Python

1. function

stay python in , Function by def keyword 、 Function name and optional parameter list definition . adopt return Keyword return value . Let's illustrate how to define and call a simple function :

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

Method body ( Of course, multi line is the same ) Is a must , Represented by indentation , Put double brackets after the method name () You can call the function

2. Scope

stay python in , Function creates a new scope .python Developers may say that functions have their own namespace , Almost one thing . This means that when a variable is encountered inside a function, the function will first look in its own namespace . Let's write a simple function to see Local scope and What is the difference between global scopes :

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

Built in functions globals Return one containing all python A dictionary of variable names that the interpreter knows ( In order to be clean and washed in vain , I omitted python Some variables created by ourselves ). stay #2 I called the function foo Print out the contents of the local scope inside the function . We can see , function foo It has its own independent namespace , Although there is nothing in the namespace for the time being .

3. Variable resolution rules

Of course, this does not mean that we cannot access external global variables in functions . stay python In the scope rules of , Creating a variable will definitely create a variable in the current scope , But when accessing or modifying variables, you will first find variables in the current scope , If no matching variable is found, it will be checked up in the closed scope in turn . So if we modify the function foo It is also possible to print the variables in the global scope by the implementation of :

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

stay #1 It's about ,python The interpreter will try to find variables a_string, Of course, it cannot be found in the local scope of the function , So I will go to the upper scope to find .
But on the other hand , Suppose we assign a value to a global variable inside a function , The result is different from what we thought :

>>> 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 , Global variables can be accessed ( If it is a variable data type ( image list,dict these ) It can even be changed ) But assignment doesn't work . Inside a function #1 It's about , We actually created a new local variable , Hide variables with the same name in the global scope . We can draw this conclusion by printing out the content in the local namespace . We can also see #2 Variables printed at a_string The value of .

4. Variable life cycle

One point worth noting is that , Variables not only exist in namespaces , They all have their own life cycle , Please see the following example :

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

1 The error at is not just caused by scope rules ( Although this is thrown NameError Cause of error ) It's also related to python And the mechanism of function call implementation in many other programming languages . In this place, there is no effective syntax for us to get variables at this execution time x Value , Because it doesn't exist at all at this time ! function foo The namespace of starts as the function call begins , End and destroy .

5. Function parameter

python Allow us to pass parameters to the function , Parameters will become local variables and exist inside the function .

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

stay Python There are many ways to define and pass parameters , The full version can be viewed python Official documents . Let's briefly explain here : Function parameters can be mandatory positional parameters or optional names , Default parameters .

>>> def foo(x, y=0): # 1
... return x - y
>>> foo(3, 1) # 2
2
>>> foo(3) # 3
3
>>> foo() # 4
Traceback (most recent call last):
...
TypeError: foo() takes at least 1 argument (0 given)
>>> foo(y=1, x=3) # 5
2

stay #1 Here we define the function foo, It has a positional parameter x And a named parameter y. stay #2 At this point, we can call functions in a conventional way , Although there is a named parameter , But parameters can still be passed to functions by position . When the function is called , For named parameters y We can also ignore it completely, like #3 As shown here . If the named parameter does not receive any value ,python The declared default value, that is 0. It should be noted that we cannot omit the first positional parameter x, Otherwise, it will be like #5 An error occurred as shown in .
At present, it is relatively simple and clear , But then it may be a little confusing .python Support named parameters when calling functions ( Personally, I think it should be named arguments ). have a look #5 Function call at , We pass two named arguments , At this time, because of the name identification , The order of parameter passing doesn't matter .
Of course, the opposite is also true : The second formal parameter of the function is y, But we pass values to it by location . stay #2 Function call at foo(3,1), We put 3 Passed to the first parameter , hold 1 Passed to the second parameter , Although the second parameter is a named parameter .
Mulberry cannot afford , It took me a long time to clarify such a simple concept : Function parameters can have names and positions . This means that the definition and calling of functions will be slightly different in understanding . We can pass named parameters to functions that only define positional parameters ( Actual parameters ), vice versa ! If you think it's not enough, you can check the official documents

6. Nested function

Python Allows the creation of nested functions . This means that we can define functions in functions, and the existing scope and variable life cycle still apply .

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

This example is a little complicated , But it looks ok . Think about it #1 What happened? :python The interpreter needs to find a person named x Local variables for , After the search fails, you will continue to search in the upper scope , The upper scope is defined in another function . For functions outer Come on , Variable x Is a local variable , But as mentioned earlier , function inner Can access closed scopes ( At least you can read and modify ).

stay #2 It's about , We call functions inner, It's very important ,inner It's just a follow python Variable name of variable resolution rule ,python The interpreter will take precedence over the outer Variable names in the scope of inner Find the matching variable .

7. The function is python First class objects in the world

Obvious , stay python Functions in are objects like everything else .( You should sing loudly here ) ah ! Functions containing variables , You are not so special !

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

You may never have thought , The function you defined has attributes . Can't , Function in python Inside is the object , Like everything else , Maybe this description will be too academic and too official : stay python in , Functions are just ordinary values, just like other values . This means that you can pass functions to other functions like parameters or return functions from functions ! If you have never thought so , 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) # 3
3
>>> apply(sub, 2, 1)
1

This example should not be very strange to you .add and sub Are two very common python function , Take two values , Return a calculated result value . stay #1 You can see that the variable ready to receive a function is just an ordinary variable , Like other variables . stay #2 We call the function passed in :“() Represents the operation of the call and the value contained in the call variable .

stay #3 It's about , You can also see that there is no special syntax for transfer functions .” The name of the function is just a table identifier like other variables .

You may have seen such behavior :“python Turn frequently used operations into functions as parameters , Like passing a function to the built-in sorting function key Parameters to customize the collation . What about treating functions as return values :

>>> 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 more strange . stay #1 I put variables that happen to be function identifiers inner Return as the return value . There is no special grammar :” Put function inner Come back out , Otherwise, it is impossible to call .“ Remember the life cycle of variables ? Each function outer When called , function inner Will be redefined , If it's not returned as a variable , It will not exist after each execution .

stay #2 At, we capture the return value - function inner, Save it in a new variable foo in . We can see , When it comes to variables foo Is evaluated , It does contain functions inner, And we can call him . It may seem a little strange for the first time , But it's not difficult to understand, is it . hold one's own , Because a strange turning point is coming soon

8. Closure

Let's not rush to define what a closure is , Let's take a look at a piece of code , Just a simple adjustment to 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 last example, we learned ,inner As a function is outer return , Save in a variable foo, And we can call it foo(). But will it work properly ? Let's first look at the scope rules .

Everything is in python Work under the scope rules of :“x Is the function outer A local variable in . Function as inner stay #1 Print at x When ,python The interpreter will be in inner Find the corresponding variables internally , Of course, I can't find , So I will go to the closed scope to find , And will find a match .

But from the life cycle of variables , How to understand ? Our variables x Is the function outer A local variable of , This means that only when the function outer Only when it is running . According to what we know python Operation mode , We can't be in a function outer Continue to call the function after returning inner, In function inner When called , Variable x It's long gone , A runtime error may occur .

Never in my wildest dreams , Return function inner It can work normally .Python Support a feature called function closure , In human terms, it is , A function nested in a non global scope can remember the closed namespace it was in when it was defined . This can be done by looking at the func_closure Attribute draws a conclusion , This attribute contains the values in the closed scope ( Only the captured values will be included , such as x, If in outer Other values are also defined , There won't be any in the closed scope )

remember , Each function outer When called , function inner Will be redefined . Now variables x The value of will not change , So every time the function returned inner It will be the same logic , What if we change it a little ?

>>> 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 closures - The closed scope remembered by the function - Can be used to create custom functions , It is essentially a hard coded parameter . In fact, we are not passing parameters 1 perhaps 2 To the function inner, We actually created various customized versions that can print various numbers .

Taking out closures alone is a very powerful function , in some ways , You might think of it as an object-oriented technology :outer Like to inner The constructor of the service ,x Like a private variable . There are many ways to use closures : If you are familiar with python Parameters of built-in sorting method key, You may have written one lambda Method sorts the list of a list based on the second element instead of the first . Now maybe you can write one itemgetter Method , Receive an index value to return a perfect function , Parameters passed to the sort function key.

however , We won't do this with closures now low What happened (⊙o⊙)…! contrary , Let's have fun again , Write a tall ornament !

9. Decorator

The decorator is actually a closure , Take a function as a parameter and return an alternative function . We look at it step by step from simple to 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_func
2

Take a closer look at the example of the decorator above . They define a function outer, It has only one some_func Parameters of , In it, we define a nested function inner.inner Will print a string , And then call some_func, stay #1 Get its return value at . stay outer Every time it's called some_func The value of may be different , But no matter some_func How about , We all call it . Last ,inner return some_func() + 1 Value - We call in #2 Stored in variable decorated The function inside can see the printed string and the return value 2, Instead of calling functions as expected foo The return value 1.

We can think of variables decorated Is the function foo A decorative version of , An enhanced version . In fact, if you plan to write a useful decorator , We may want to be willing to completely replace the original function with a decorative version foo, In this way, we will always get our ” Enhanced Edition “foo. To achieve this effect , There is no need to learn new grammar , Simply assign values to variables foo That's it :

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

Now? , Any call will not involve the original function foo, Will get a new decorative version foo, Now let's write a useful decorator .

Imagine that we have a library , This library can provide objects with similar coordinates , Maybe they are just some x and y Coordinate pairs of . Unfortunately, these coordinate objects do not support mathematical operators , And we can't modify the source code , Therefore, you cannot directly add the support of operators . We will do a series of mathematical operations , So we want a function that can add and subtract two coordinate objects properly , These methods are easy to write :

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

Unfortunately, our addition and subtraction function also needs some boundary checking behavior, so what should we do ? Maybe you can only add or subtract the positive coordinate objects , Any returned value should also be a positive coordinate . So now the expectation is like 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 that without changing the coordinate object one, two, three Under the premise of one subtract two The value of is {x: 0, y: 0},one add three The value of is {x: 100, y: 200}. Instead of adding parameters and return value boundary checking logic to each method , Let's write a border 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, two)
Coord: {'y': 0, 'x': 0}
>>> add(one, three)
Coord: {'y': 200, 'x': 100}

IDE demonstration

'''
No one answers the problems encountered in learning ? Xiaobian created a Python Exchange of learning QQ Group :711312441
Looking for small partners who share the same aspiration , Help each other , There are also good video tutorials and PDF e-book !
'''
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)
print(add(one, two))
#Coord: {'y': 400, 'x': 400}
print("------------------------------------")
one = Coordinate(100, 200)
two = Coordinate(300, 200)
three = Coordinate(-100, -100)
print(sub(one, two))
#Coord: {'y': 0, 'x': -200}
print(add(one, three))
#Coord: {'y': 100, '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 = 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)
print(sub(one, two))
#Coord: {'y': 0, 'x': 0}
print(add(one, three))
#Coord: {'y': 200, 'x': 100}

result :

Coord: {'x': 400, 'y': 400}
------------------------------------
Coord: {'x': -200, 'y': 0}
Coord: {'x': 0, 'y': 100}
------------------------------------
Coord: {'x': 0, 'y': 0}
Coord: {'x': 100, 'y': 200}

This decorator can work like the previous decorator example , Return a modified function , But in this case , It can do some very useful checking and formatting work on the input parameters and return values of functions , The negative value of x and y Replace with 0.
Obvious , In this way , Our code becomes more concise : Isolate the logic of boundary checking into separate methods , Then it is applied to the places we need to check through decorator packaging . Another way to achieve the same goal is to call the boundary check method at the beginning of the calculation method and before the return value . But what cannot be denied is , Using decorators enables us to achieve the purpose of coordinate boundary checking with the minimum amount of code . in fact , If we are decorating our own defined methods , We can make the application of decorators more compelling .

10. Use @ Identifiers apply decorators to functions

Python2.4 Support the use of identifiers @ Apply decorators to functions , Just add @ And the name of the decorator . In the example of the previous section, we replaced the original method with the decorated method :

>>> add = wrapper(add)

This method can package any method at any time . But if we customize a method , We can use @ Decorate :

>>> @wrapper
... def add(a, b):
... return Coordinate(a.x + b.x, a.y + b.y)

What we need to understand is , This approach is the same as the previous simple replacement of the original method with packaging , python Just add some grammar sugar to make the decorative behavior more direct, clear and elegant .

def login(func): # Pass in the parameter func = tv
def inner(arg):
print("passed user verifcation...")
func(arg)
return inner
def tv(name):
print("Welcome [%s] to TV page" % (name))
tv = login(tv)
tv(("Amu"))

result :

passed user verifcation...
Welcome [Amu] to TV page

Use identifiers @ Apply decorators to functions

def login(func): # Pass in the parameter func = tv
def inner(arg):
print("passed user verifcation...")
func(arg)
return inner
@login # Decorator Use identifiers @ Apply decorators to functions
def tv(name):
print("Welcome [%s] to TV page" % (name))
#tv = login(tv)
tv(("Amu"))

result :

passed user verifcation...
Welcome [Amu] to TV page

11. *args and **kwargs

We have finished a useful decorator , However, due to hard coding, it can only be applied to a specific class of methods , This kind of method takes two parameters , Functions passed to closure capture . What should we do if we want to implement a decorator that can be applied to any method ? Another example , If we want to implement a counter like decorator that can be applied to any method , There is no need to change any logic of the original method . This means that decorators can accept functions with any signature as their decorated methods , At the same time, it can call the decorated method with the parameters passed to it .

It's a great coincidence that Python There is a syntax that supports this feature . You can read Python Tutorial Get more details . When defining functions, we use , Means that those parameters passed by location will be placed with Prefixed variables , 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 Just to put it simply, any passed position parameters are printed out , You can see , In the code #1 Here we just refer to the variables in the function args, *args It is only used in function definition to indicate that location parameters should be stored in variables args Inside .Python Allow us to set some parameters and pass args Capture all other remaining position parameters that are not captured , It's like #2 As shown here .

* Operators can also be used when a function is called . The meaning is basically the same . When a function is called , One use * The content that needs to be extracted as a variable is then used as a flag . alike , Take an example :

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

1 Code and #2 The code at does the same thing , stay #2 It's about ,python What we do for us can also be done manually . It's not a bad thing ,*args Or it means that when the calling method is large, additional parameters can be obtained from an iterative list , Or when defining a method, mark that the method can accept arbitrary location parameters .

What follows is ** It will be a little more complicated ,** Parameter dictionary representing key value pairs , and * The meaning represented is almost the same , It's also very simple, isn't it :

'''
No one answers the problems encountered in learning ? Xiaobian created a Python Learning exchange group :711312441
Looking for small partners who share the same aspiration , Help each other , There are also good video tutorials and PDF e-book !
'''
>>> 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 show , All uncapped keyword parameters should be stored in kwargs In the dictionary . As mentioned before ,argshe kwargs Not at all python Part of grammar , But when defining functions , Using such variable names is an unwritten convention . and * equally , We can also use when defining or calling functions **.

>>> dct = {'x': 1, 'y': 2}
>>> def bar(x, y):
... return x + y
>>> bar(**dct)
3

12. More versatile decorators

With this new skill , We can easily write a decorator that can record the parameters passed to the function . Let's start with an example of simply outputting logs 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

Please note our function inner, It can accept any number and type of parameters and pass them to the wrapped method , This allows us to decorate any method with this decorator .

>>> @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 , The corresponding log will also be printed to the output window , As we expected .


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved