您的位置:首页 > 编程语言 > Python开发

Python之美[从菜鸟到高手]--装饰器之使用情景分析

2016-09-29 22:32 453 查看
有这个一个需求,统计一个函数执行时间 ? 方案很多,但无疑使用装饰器是一种好的方案。

[python]
view plain
copy

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)怎么办?

这就轮到带参数的装饰器出场了。

[python]
view plain
copy

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++类型检查是在编译时,而我们的参数类型检查是在运行时。

[python]
view plain
copy

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)时,程序将输出:

[python]
view plain
copy

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语句提供了上下文装饰器。如:

[python]
view plain
copy

with open('proxy.py') as f:  
    for x in f:  
        print x  

   要使用with语句,需要实现__enter__和__exit__方法,__enter__是在进入时调用,__exit__是在退出时调用。其中as w的w就是__enter__函数

返回的对象。

比如,在数据库操作中,如果抛出异常,那么我们要rollback,成功就commit。用with语句实现如下:

[python]
view plain
copy

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装饰器重写上面的代码如下:

[python]
view plain
copy

@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方法(只限学习)

[python]
view plain
copy

@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 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: