• 技术文章 >后端开发 >Python教程

    浅析Python编写函数装饰器

    2016-06-10 15:05:39原创916
    编写函数装饰器

    本节主要介绍编写函数装饰器的相关内容。

    跟踪调用

    如下代码定义并应用一个函数装饰器,来统计对装饰的函数的调用次数,并且针对每一次调用打印跟踪信息。

    class tracer:
    def __init__(self,func):
    self.calls = 0
    self.func = func
    def __call__(self,*args):
    self.calls += 1
    print('call %s to %s' %(self.calls, self.func.__name__))
    self.func(*args)
    @tracer
    def spam(a, b, c):
    print(a + b + c)

    这是一个通过类装饰的语法写成的装饰器,测试如下:

    >>> spam(1,2,3)
    call 1 to spam
    6
    >>> spam('a','b','c')
    call 2 to spam
    abc
    >>> spam.calls
    2
    >>> spam
    <__main__.tracer object at 0x03098410>

    运行的时候,tracer类和装饰的函数分开保存,并且拦截对装饰的函数的随后的调用,以便添加一个逻辑层来统计和打印每次调用。

    装饰之后,spam实际上是tracer类的一个实例。

    @装饰器语法避免了直接地意外调用最初的函数。考虑如下所示的非装饰器的对等代码:

    calls = 0
    def tracer(func,*args):
    global calls
    calls += 1
    print('call %s to %s'%(calls,func.__name__))
    func(*args)
    def spam(a,b,c):
    print(a+b+c)

    测试如下:

    ?
    1
    2
    3
    4
    5
    >>> spam(1,2,3)
    6
    >>> tracer(spam,1,2,3)
    call 1 to spam
    6

    这一替代方法可以用在任何函数上,且不需要特殊的@语法,但是和装饰器版本不同,它在代码中调用函数的每个地方都需要额外的语法。尽管装饰器不是必需的,但是它们通常是最为方便的。

    扩展——支持关键字参数

    下述代码时前面例子的扩展版本,添加了对关键字参数的支持:

    class tracer:
    def __init__(self,func):
    self.calls = 0
    self.func = func
    def __call__(self,*args,**kargs):
    self.calls += 1
    print('call %s to %s' %(self.calls, self.func.__name__))
    self.func(*args,**kargs)
    @tracer
    def spam(a, b, c):
    print(a + b + c)
    @tracer
    def egg(x,y):
    print(x**y)

    测试如下:

    >>> spam(1,2,3)
    call 1 to spam
    6
    >>> spam(a=4,b=5,c=6)
    call 2 to spam
    15
    >>> egg(2,16)
    call 1 to egg
    65536
    >>> egg(4,y=4)
    call 2 to egg
    256

    也可以看到,这里的代码同样使用【类实例属性】来保存状态,即调用的次数self.calls。包装的函数和调用计数器都是针对每个实例的信息。

    使用def函数语法写装饰器

    使用def定义装饰器函数也可以实现相同的效果。但是有一个问题,我们也需要封闭作用域中的一个计数器,它随着每次调用而更改。我们可以很自然地想到全局变量,如下:

    calls = 0
    def tracer(func):
    def wrapper(*args,**kargs):
    global calls
    calls += 1
    print('call %s to %s'%(calls,func.__name__))
    return func(*args,**kargs)
    return wrapper
    @tracer
    def spam(a,b,c):
    print(a+b+c)
    @tracer
    def egg(x,y):
    print(x**y)

    这里calls定义为全局变量,它是跨程序的,是属于整个模块的,而不是针对每个函数的,这样的话,对于任何跟踪的函数调用,计数器都会递增,如下测试:

    >>> spam(1,2,3)
    call 1 to spam
    6
    >>> spam(a=4,b=5,c=6)
    call 2 to spam
    15
    >>> egg(2,16)
    call 3 to egg
    65536
    >>> egg(4,y=4)
    call 4 to egg
    256

    可以看到针对spam函数和egg函数,程序用的是同一个计数器。

    那么如何实现针对每一个函数的计数器呢,我们可以使用Python3中新增的nonlocal语句,如下:

    def tracer(func):
    calls = 0
    def wrapper(*args,**kargs):
    nonlocal calls
    calls += 1
    print('call %s to %s'%(calls,func.__name__))
    return func(*args,**kargs)
    return wrapper
    @tracer
    def spam(a,b,c):
    print(a+b+c)
    @tracer
    def egg(x,y):
    print(x**y)
    spam(1,2,3)
    spam(a=4,b=5,c=6)
    egg(2,16)
    egg(4,y=4)

    运行如下:

    call 1 to spam
    6
    call 2 to spam
    15
    call 1 to egg
    65536
    call 2 to egg
    256

    这样,将calls变量定义在tracer函数内部,使之存在于一个封闭的函数作用域中,之后通过nonlocal语句来修改这个作用域,修改这个calls变量。如此便可以实现我们所需求的功能。

    陷阱:装饰类方法

    【注意,使用类编写的装饰器不能用于装饰某一类中带self参数的的函数,这一点在Python装饰器基础中介绍过】
    即如果装饰器是如下使用类编写的:

    class tracer:
    def __init__(self,func):
    self.calls = 0
    self.func = func
    def __call__(self,*args,**kargs):
    self.calls += 1
    print('call %s to %s'%(self.calls,self.func.__name__))
    return self.func(*args,**kargs)

    当它装饰如下在类中的方法时:

    class Person:
    def __init__(self,name,pay):
    self.name = name
    self.pay = pay
    @tracer
    def giveRaise(self,percent):
    self.pay *= (1.0 + percent)

    这时程序肯定会出错。问题的根源在于,tracer类的__call__方法的self——它是一个tracer实例,当我们用__call__把装饰方法名重绑定到一个类实例对象的时候,Python只向self传递了tracer实例,它根本没有在参数列表中传递Person主体。此外,由于tracer不知道我们要用方法调用处理的Person实例的任何信息,没有办法创建一个带有一个实例的绑定的方法,所以也就没有办法正确地分配调用。

    这时我们只能通过嵌套函数的方法来编写装饰器。

    计时调用

    下面这个装饰器将对一个装饰的函数的调用进行计时——既有针对一次调用的时间,也有所有调用的总的时间。

    import time
    class timer:
    def __init__(self,func):
    self.func = func
    self.alltime = 0
    def __call__(self,*args,**kargs):
    start = time.clock()
    result = self.func(*args,**kargs)
    elapsed = time.clock()- start
    self.alltime += elapsed
    print('%s:%.5f,%.5f'%(self.func.__name__,elapsed,self.alltime))
    return result
    @timer
    def listcomp(N):
    return [x*2 for x in range(N)]
    @timer
    def mapcall(N):
    return list(map((lambda x :x*2),range(N)))
    result = listcomp(5)
    listcomp(50000)
    listcomp(500000)
    listcomp(1000000)
    print(result)
    print('allTime = %s'%listcomp.alltime)
    print('')
    result = mapcall(5)
    mapcall(50000)
    mapcall(500000)
    mapcall(1000000)
    print(result)
    print('allTime = %s'%mapcall.alltime)
    print('map/comp = %s '% round(mapcall.alltime/listcomp.alltime,3))

    运行结果如下:

    listcomp:0.00001,0.00001
    listcomp:0.00885,0.00886
    listcomp:0.05935,0.06821
    listcomp:0.11445,0.18266
    [0, 2, 4, 6, 8]
    allTime = 0.18266365607537918
    mapcall:0.00002,0.00002
    mapcall:0.00689,0.00690
    mapcall:0.08348,0.09038
    mapcall:0.16906,0.25944
    [0, 2, 4, 6, 8]
    allTime = 0.2594409060462425
    map/comp = 1.42

    这里要注意的是,map操作在Python3中返回一个迭代器,所以它的map操作不能和一个列表解析的工作直接对应,即实际上它并不花时间。所以要使用list(map())来迫使它像列表解析那样构建一个列表

    添加装饰器参数

    有时我们需要装饰器来做一个额外的工作,比如提供一个输出标签并且可以打开或关闭跟踪消息。这就需要用到装饰器参数了,我们可以使用装饰器参数来制定配置选项,这些选项可以根据每个装饰的函数而编码。例如,像下面这样添加标签:

    def timer(label = ''):
    def decorator(func):
    def onCall(*args):
    ...
    print(label,...)
    return onCall
    return decorator
    @timer('==>')
    def listcomp(N):...

    我们可以将这样的结果用于计时器中,来允许在装饰的时候传入一个标签和一个跟踪控制标志。比如,下面这段代码:

    import time
    def timer(label= '', trace=True):
    class Timer:
    def __init__(self,func):
    self.func = func
    self.alltime = 0
    def __call__(self,*args,**kargs):
    start = time.clock()
    result = self.func(*args,**kargs)
    elapsed = time.clock() - start
    self.alltime += elapsed
    if trace:
    ft = '%s %s:%.5f,%.5f'
    values = (label,self.func.__name__,elapsed,self.alltime)
    print(format % value)
    return result
    return Timer

    这个计时函数装饰器可以用于任何函数,在模块中和交互模式下都可以。我们可以在交互模式下测试,如下:

    >>> @timer(trace = False)
    def listcomp(N):
    return [x * 2 for x in range(N)]
    >>> x = listcomp(5000)
    >>> x = listcomp(5000)
    >>> x = listcomp(5000)
    >>> listcomp
    <__main__.timer..Timer object at 0x036DCC10>
    >>> listcomp.alltime
    0.0011475424533080223
    >>>
    >>> @timer(trace=True,label='\t=>')
    def listcomp(N):
    return [x * 2 for x in range(N)]
    >>> x = listcomp(5000)
    => listcomp:0.00036,0.00036
    >>> x = listcomp(5000)
    => listcomp:0.00034,0.00070
    >>> x = listcomp(5000)
    => listcomp:0.00034,0.00104
    >>> listcomp.alltime
    0.0010432902706075842

    有关Python编写函数装饰器相关知识小编就给大家介绍到这里,希望对大家有所帮助!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    20期PHP线上班

    相关文章推荐

    • 【活动】充值PHP中文网VIP即送云服务器• 深入了解python中的代码缩进规则• Python随机森林模型实例详解• 一文掌握Python返回函数、闭包、装饰器、偏函数• Python可视化总结之matplotlib.pyplot基本参数详解• python能代替JavaScript吗
    1/1

    PHP中文网