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

有这个一个需求,统计一个函数执行时间 ? 方案很多,但无疑使用装饰器是一种好的方案。

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


  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值