- 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