有这个一个需求,统计一个函数执行时间 ? 方案很多,但无疑使用装饰器是一种好的方案。
def timer(func):
def _timer(*args,**kwargs): #参数是函数调用传递过来的参数
begin=time.time()
func(*args,**kwargs)
time.sleep(2)
print time.time()-begin
return _timer
@timer
def log(info):
print info
#log=timer(log)
log('sleep few seconds')
输出结果:
sleep few seconds
2.0
其实,@timer是Python提供的一个语法糖,实际等效于log=timer(log)
很明显我们创建的是不带参数的timer,所以timer中的参数肯定是我们需要调用的函数,那么调用函数参数肯定是通过类部的_timer传递,
所以在主函数中需要返回函数对象。
将log函数带入->>>> log=timer(log('sleep few seconds')),是不是很简单。
上面的测试函数执行时间,我人为的sleep几秒(不然打印的是0),那我要sleep(1)怎么办?
这就轮到带参数的装饰器出场了。
def htimer(minuts):
def _htimer(func):
def _deco(*args,**kwargs):
begin=time.time()
time.sleep(minuts)
func(*args,**kwargs)
print time.time()-begin
return _deco
return _htimer
@htimer(1)
def logh(info):
print info
logh('sleep few seconds')
程序输出:
sleep few seconds
1.0
所谓的带参数装饰器,其实就是内部多了个返回函数对象,不然参数怎么传递进去呢?
我们将上面的logh函数带入可清晰的看出调用过程: logh=htimer(1)(logh(‘sleep few seconds'))
刚接触装饰器的童鞋觉得比较难,其实还是装饰器中各个函数的参数到底是什么?通过不带参数和带参数的装饰器编写,我们可以看出,
只要和调用时传递参数的顺序一致就可以了。
如上面,@htimer(1)
def logh(info):
pass
首先传递给htimer的是暂停时间,所以装饰器的最外层函数参数就是暂停时间;下面传递的函数logh,所以类部函数参数为logh函数对象;最后的是info输出信息,所以最内层的参数肯定是输出信息。
那么装饰器到底有哪些使用情景呢?常见的装饰器模式有参数检查,缓存,代理,和上下文提供者。下面就将使用上面讲述的基础知识,实现常见模式。
一:参数检查
我们知道Python是弱类型语言,可我们有时又需要对参数进行判断,防止调用出错,所以参数检查就应用在这种情景下。和C/C++不同的是,C/C++类型检查是在编译时,而我们的参数类型检查是在运行时。
def chtype(type):
def _chtype(fun):
def _deco(*args):
if len(type)!=len(args):
raise Exception('args error')
for arg,arg_type in itertools.izip(args,type):
if not isinstance(arg,arg_type):
raise TypeError('%s is not the type of %s' %(arg,arg_type))
fun(*args)
return _deco
return _chtype
@chtype((str,str))
def login(name,passwd):
print 'login ok'
程序将输出: login ok
当我们调用:login('skycrab',22)时,程序将输出:
Traceback (most recent call last):
File "F:\python workspace\Pytest\src\cs.py", line 80, in <module>
login('skycrab',22)
File "F:\python workspace\Pytest\src\cs.py", line 70, in _deco
raise TypeError('%s is not the type of %s' %(arg,arg_type))
TypeError: 22 is not the type of <type 'str'>
代码写的简单易读,如果上面基础知识都懂了,那么写这个参数检查也花不了多少时间。同时可以用这个证明一下上面所说的装饰器函数参数顺序。
缓存,代理和参数检查代码类似,我们重点看看上下文提供者。
二:上下文提供者
所谓的上下文提供者也就是负责一块代码块中的资源,在进入代码时申请资源,在退出代码时销毁资源。说白了就是try,finally的变体。
with语句提供了上下文装饰器。如:
with open('proxy.py') as f:
for x in f:
print x
要使用with语句,需要实现__enter__和__exit__方法,__enter__是在进入时调用,__exit__是在退出时调用。其中as w的w就是__enter__函数
返回的对象。
比如,在数据库操作中,如果抛出异常,那么我们要rollback,成功就commit。用with语句实现如下:
class Execute:
def __enter__(self):
self.cursor=MySQLdb.connect(host="localhost",user="root",passwd="",db="test",charset="utf8").cursor()
return self.cursor
def __exit__(self,exception_type,exception_value,exception_traceback):
if exception_type is None:
self.cursor.commit()
else:
slef.cursor.rollback()
#return True 返回True将不抛出异常
with Execute() as w:
w.execute(33)
而要实现with语句必须实现上述两个方法,比较麻烦,这时就可以使用装饰器了。
标准库模块contextlib给with语句提供了方便。其中contextmanager装饰器,增强了以yield语句分开的__enter__和__exit__两部分的生成器。
使用contextmanager装饰器重写上面的代码如下:
@contextmanager
def myexecute():
cursor=MySQLdb.connect(host="localhost",user="root",passwd="",db="test",charset="utf8").cursor()
try:
yield cursor
except:
cursor.rollback()
else:
cursor.commit()
with myexecute() as f:
f.execute('sql query')
我们可以清楚的看到contextmanager装饰器的方便,上述as f 的f 就是yield cursor传过来的对象。yield之后的部分其实就类似__exit__中的代码。
既然熟悉了contextmanager装饰器,我们可以自己包装open方法(只限学习)
@contextmanager
def myfile(name):
f=open(name)
try:
yield f
finally:
print 'finally'
f.close()
with myfile('test.py') as f:
for x in f:
print x