Python装饰器

0x00 语法糖

装饰器是Python一个语法糖,装饰器就是在不改变原函数的基础上,在原函数前后执行一些代码,就好像在原函数上增加了一些装饰品一样。这个语法能非常方便给一个函数增加一些功能,例如权限控制,校验一个函数的输入等。

0x01 语法原理

最简单的情况

装饰一个有参数的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
def debug(func):
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
print "function "+func.__name__+" return "+str(res)
return res
return wrapper
@debug
def sum(a, b):
return a + b
if __name__=="__main__":
print sum(1,3)

运行结果

1
2
function sum return 4
4

这里展示了装饰器最基本的使用,给sum函数添加了debug这个装饰,这个装饰器在sum函数调用之后输出了函数名和返回值方便调试。我们看一下这个语法糖的真面目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def debug(func):
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
print "function "+func.__name__+" return "+str(res)
return res
return wrapper
def sum(a, b):
return a + b
if __name__=="__main__":
print debug(sum)(1,3)
print "==== function ===="
print debug
print debug(sum)

运行结果为

1
2
3
4
5
function sum return 4
4
==== function ====
<function debug at 0x1044c0de8>
<function wrapper at 0x1044c0ed8>

在有装饰器的时候,调用sum(1,3)相当于调用了debug(sum)(1,3)。我们来分析一下debug(sum)(1,3)这个调用。在Python中,万物皆对象,类是一个对象,方法也是一个对象。例如:

1
2
def sum(a, b):
return a + b

定义了一个sum方法,同时,sum也是一个对象,它的类型是函数。sum的值是一个入口在某地址的函数。sum(1,2)就是对这个函数的调用。我们回到debug(sum)(1,3)这个函数调用,首先调用了debug这个函数,它的参数是一个函数,我们根据

1
2
3
4
5
6
def debug(func):
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
print "function "+func.__name__+" return "+str(res)
return res
return wrapper

return wrapper表示这个函数的返回值是一个函数,即debug(sum)(1,3)相当于wrapper(1,3)。wrapper(1,3)即对wrapper函数的调用。这样,把装饰器函数的定义和语法糖背后的代码结合理解,很容易理解装饰器这个概念和@debug这样的语法。

带参数的情况

装饰器也可以带参数,这里可能比上面稍微复杂一点,如果上面的还有一点疑惑,多看两遍,在看接下来的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
is_debug=True
def debug(is_debug):
def wrapper(func):
def inner(*args,**kwargs):
res=func(*args,**kwargs)
if(is_debug):
print "function " + func.__name__ + " return " + str(res)
return res
return inner
return wrapper
@debug(is_debug)
def sum(a, b):
return a + b
if __name__=="__main__":
print sum(1,3)

在这里,我们定义了一个全局变量is_debug来控制是否输出debug信息,这个值是装饰器的参数。感觉在装饰器定义多层函数,还有不同的参数,完全分不清啊有木有!
我们来看与上面等价的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
is_debug=True
def debug(is_debug):
def wrapper(func):
def inner(*args,**kwargs):
res=func(*args,**kwargs)
if(is_debug):
print "function " + func.__name__ + " return " + str(res)
return res
return inner
return wrapper
def sum(a, b):
return a + b
if __name__=="__main__":
print debug(is_debug)(sum)(1,3)
print "==== function ===="
print debug
print debug(is_debug)
print debug(is_debug)(sum)

下面是运行结果

1
2
3
4
5
6
function sum return 4
4
==== function ====
<function debug at 0x10b079e60>
<function wrapper at 0x10b079f50>
<function inner at 0x10b08e050>

其实,看懂这个调用的方法跟上面是一样的。不再赘述,值得一提的是这里,其实debug(is_debug)返回的这个函数可以认为是另一个没有参数的装饰器,然后整个结构就上面没有参数的情况一样了。这个新的装饰器的参数是函数,即debug(is_debug)(sum),然后返回一个参数是args,*kwargs的函数,再调用这个函数,参数为1,3。即完成整个调用。