python中decorator使用形式基本上分为两种:
1. | 2.
@dec_no_arguments | @dec_with_arguments(arg)
def f(*args, **kwargs): | def f(*args, **kwargs):
pass | pass
第一种使用方式是不带参数的装饰器,第二种是带参数的装饰器。先说不带参数的
装饰器。
example1:
1 #coding: utf-8
2
3 import time
4 import traceback
5 import urllib2
6
7 def timeit(func):
8 def __(*args, **kwargs):
9 t1 = time.time()
10 try:
11 ret = func(*args, **kwargs)
12 except Exception, e:
13 print tracebak.format_exc()
14 return ''
15 else:
16 return ret
17 finally:
18 t2 = time.time()
19 print 'call the function:%s spend time:%d ms' % (func.__name__, int((t2-t1) * 1000))
20 return ret
21 return __
22
23 @timeit
24 def read_from_net(url):
25 return urllib2.urlopen(url).read()
26
27 if __name__ == '__main__':
28 ret = read_from_net('http://www.baidu.com')
29 #do-something......</span>
第23行代码使用了无参数的timeit装饰器函数。python解释器在转化无参数的decorator
时使用如下的形式:
origin_function_name = decorator(origin_function_name)
origin_function_name是被装饰的函数的名称,直白点说就是@符号所在行的下一行的函
数名称。在example 1中就是read_from_net函数,decorator就是@符号后面紧跟着的所有
内容。在example 1中就是timeit函数。一个值得注意的点是,这个转化是在python导入的
时候发生的而非代码在执行的时候才转化,如下例子就是一个很好的说明。
example 2 (decorator.py):
1 #coding: utf-8
2
3 import time
4 import urllib2
5
6 def timeit(func):
7 print 'this is in the timeit function'
8 def __(*args, **kwargs):
9 t1 = time.time()
10 ret = func(*args, **kwargs)
11 t2 = time.time()
12 print 'call the function:%s spend time:%d ms' % (func.__name__, int((t2-t1) * 1000))
13 return ret
14 return __
15
16 @timeit
17 def read_from_net(url):
18 return urllib2.urlopen(url).read()
19
20 if __name__ == '__main__':
21 pass</span>
在终端中执行python decorator.py,输出如下:
this is in the timeit function
代码中并没有调用read_from_net函数,但是装饰器函数(这里指timeit函数)却被执行了,也就
是说转化发生在导入的时候。
当然装饰器不仅仅可以是函数,也可以是类,将上面的timeit代码改写成类,如下代码:
example 3:
1 #coding: utf-8
2
3 import time
4 import urllib2
5
6 def timeit(func):
7 print 'this is in the timeit function'
8 def __(*args, **kwargs):
9 t1 = time.time()
10 ret = func(*args, **kwargs)
11 t2 = time.time()
12 print 'call the function:%s spend time:%d ms' % (func.__name__, int((t2-t1) * 1000))
13 return ret
14 return __
15
16 class TimeIt(object):
17 def __init__(self, func):
18 self.func = func
19 def __call__(self, *args, **kwargs):
20 t1 = time.time()
21 ret = self.func(*args, **kwargs)
22 t2 = time.time()
23 print 'call the function:%s spend time:%d ms' % (self.func.__name__, int((t2-t1) * 1000))
24 return ret
25
26 @TimeIt
27 def read_from_net(url):
28 return urllib2.urlopen(url).read()
29
30 if __name__ == '__main__':
31 ret = read_from_net('http://www.baidu.com/')
32 #do-something
TimeIt和timeit函数的装饰功能是一样的,只不过实现的方式不一样而已。使用类作为装
饰器
同样也遵守了上面提到的转化形式。
origin_function_name = decorator(origin_function_name)
origin_function_name就是read_from_net
decorator是TimIt,替换相应的名称就是:read_from_net = TimeIt(read_from_net),竟然
毫无违和感,只是现在read_from_net是一个类了而已。read_from_net的调用方式并没有发
生任何的改变,因为类实现了__call__函数,这样对象也可以像正常的函数一样使用了。python
标准模块functools中的partial就是一个类作为装饰器,functools相关功能说明的链接是https://docs.python.org/2/library/functools.html
说完了不带参数的装饰器,下面该说带参数的装饰器了。
example 4:
#coding: utf-8
import time
import urllib2
import traceback
ALLOW_IP_LIST = ('127.0.0.1', '192.168.*.*, 172.10.100.*')
class IPError(Exception):
pass
def check_ip(allow_ip_list=None):
allow_ip_list = allow_ip_list or ALLOW_IP_LIST
def _dec1(func):
def __dec2(self, *args, **kwargs):
print '__dec2.__dict__', __dec2.__dict__
cli_ip_frag = self.ip.split('.')
for ip in allow_ip_list:
ip_frag = ip.split('.')
for i in range(len(ip_frag)):
if ip_frag[i] == '*':
continue
if ip_frag[i] != cli_ip_frag[i]:
break
else:
return func(self, *args, **kwargs)
raise IPError, self.ip
return __dec2
return _dec1
class CheckIp(object):
def __init__(self, allow_ip_list=None):
self.allow_ip_list = allow_ip_list or ALLOW_IP_LIST
def __call__(self, func):
allow_ip_list = self.allow_ip_list
def __dec1(self, *args, **kwargs):
cli_ip_frag = self.ip.split('.')
for ip in allow_ip_list:
ip_frag = ip.split('.')
for i in range(len(ip_frag)):
if ip_frag[i] == '*':
continue
if ip_frag[i] != cli_ip_frag[i]:
break
else:
return func(self, *args, **kwargs)
raise IPError, self.ip
return __dec1
class Ping(object):
def __init__(self, ip=None):
self.ip = ip or ALLOW_IP_LIST[0]
@check_ip()
def get(self):
return 'OK'
@CheckIp()
def post(self):
return 'OK'
if __name__ == '__main__':
p1 = Ping()
print '.......................p1.get.....................'
print 'p1.get', p1.get()
print '......................p1.post....................'
print 'p1.post', p1.post()
p2 = Ping(ip='10.25.10.1')
try:
print '...................p2.get..................'
p2.get()
except IPError, e:
print traceback.format_exc()
finally:
print '................p2.post...................'
p2.post()
<span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px; background-color: rgb(255, 255, 255);">在终端中执行输出的结果为:</span>
.......................p1.get.....................
p1.get OK
......................p1.post....................
p1.post OK
...................p2.get..................
Traceback (most recent call last):
File "decorator.py", line 69, in <module>
p2.get()
File "decorator.py", line 25, in __dec2
raise IPError, self.ip
IPError: 10.25.10.1
................p2.post...................
Traceback (most recent call last):
File "decorator.py", line 74, in <module>
p2.post()
File "decorator.py", line 45, in __dec1
raise IPError, self.ip
__main__.IPError: 10.25.10.1
example 4中使用了带参数的装饰器,分别用类和函数实现了装饰器。python在转化装饰器时
使用的形式和无参数形式是一样的。
origin_function_name = decorator(origin_function_name)
就拿get方法举例:
origin_function_name 是get,
decorator是check_ip(allow_ip_list)注意不是check_ip,这里的decorator正如刚开始描述的:
是@符号后面所有的内容,而这里@符号后面的是check_ip()而非check_ip。
所以替换后表达式如下:
get = check_ip()(get)
一般而言,对于一个被装饰器装饰的函数,在python解释器导入阶段就进行了转化,转化
之后的对象是否可调用,完全在于你的装饰器怎么实现。按照函数执行的观点来解释则简
单了很多,比如get方法的装饰,转化后是get = check_ip()(get),按照函数一个一个的调用
check_ip()返回了__dec1,则进一步有get = __dec1(get),然后再次执行__dec1(get)函
数的返回结果get = __dec2到这一步就是个普通的赋值了,那么get此时其实就是__dec2
只不过在转化的过程中,有些参数被保留在了闭包里,这样,直接调用get的时候,相当于执
行了闭包中的代码,对于一些共同的逻辑就能够提炼成一个decorator,方便使用,且使得代
码更加的简洁。
在使用check_ip装饰器的时候,注意不能写成@check_ip,即使不需要参数,check_ip后面的
括号还是需要的,不然转换就成这样的了:
get = check_ip(get) 转化之后get = _dec1
虽然这是合法的,调用的时候也没语法错误,但是显然不是我们期望的!为什么能成功呢,我们
看一下就知道了,get被转化成了_dec1函数,但我们使用p1.get()的时候,其实调用了__dec1函
数,而__dec1函数刚好只有一个参数self,所以代码顺利的执行了,但是实际get的代码体没执行。
这个例子貌似很巧啊。
类和普通函数都能作为装饰器,个人更喜欢使用类做装饰器,感觉更清晰,特别是对于有参数的
装饰器,里面套了三层,简直是有点晕啊,使用类,嵌套貌似少了一层。