python函数装饰器的实现_Python函数装饰器实现方法详解

本文实例讲述了Python函数装饰器实现方法。分享给大家供大家参考,具体如下:

编写函数装饰器

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

跟踪调用

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

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

运行的时候,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)

测试如下:

>>> 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程序设计有所帮助。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
函数装饰器Python中一种特殊的语法,可以用来修改、扩展或包装其他函数的功能。装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数作为结果。 装饰器的语法使用了@符号,它放在要修饰的函数定义之前。当调用被修饰的函数时,实际上是调用了装饰器返回的新函数。 下面是一个简单的装饰器示例: ```python def decorator(func): def wrapper(): print("Before function execution") func() print("After function execution") return wrapper @decorator def say_hello(): print("Hello, world!") say_hello() ``` 在这个例子中,`decorator`是一个装饰器函数,它接受一个函数作为参数,并定义了一个内部函数`wrapper`来包装原始函数。`wrapper`函数在调用原始函数前后分别打印了一些信息。 通过在`say_hello`函数定义之前添加`@decorator`语法,我们将`say_hello`函数传递给`decorator`装饰器,并将返回的新函数赋值给`say_hello`。因此,当我们调用`say_hello`时,实际上是调用了被修饰后的函数`wrapper`。 这样,每次调用`say_hello`函数时,都会在执行前后打印一些信息。 装饰器可以用于很多场景,比如日志记录、性能分析、权限检查等。它们提供了一种简洁而优雅的方式来修改函数的行为,而无需修改函数本身的定义。同时,装饰器还可以堆叠使用,即一个函数可以被多个装饰器修饰。 希望这个简单的示例能够帮助你理解Python函数装饰器的基本概念和用法。如果你有更多的问题,可以继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值