1: 函数装饰器
在介绍函数装饰器之前,我们来看下下面的一个小需求:对功能相同但不同算法实现的两个函数的运行时间的比较,或者业务代码中要对某些函数执行时间的统计。下面以判断某个整数是否为素数为例:
判断素数代码如下:
import timefrom math import sqrt def isPrimes1(n): if n return False for i in range(2, int(sqrt(n) + 1)): if n % i == 0: return False return True def isPrimes2(n): if n > 1: if n == 2: return True if n % 2 == 0: return False for x in range(3, int(sqrt(n) + 1), 2): if n % x == 0: return False return True return False
低级版本:在调用之前记下开始时间,调用结束时,使用当前时间减去开始时间,代码如下:
import timestart_time = time.time()print(isPrimes1(32212254719))print('isPrimes1(32212254719) 运行时间:',time.time() - start_time) print('*'*30)start_time = time.time()print(isPrimes2(32212254719))print('isPrimes2(32212254719) 运行时间:',time.time() - start_time)
按照这种方式来统计的话,如果实际业务中有N多个地方调用了,你就得修改N多次(最要命的是一不小心,你还能整出bug来),并且代码都是一样的,也就是冗余的。我们之前有学过函数,即对相同功能的封装,那么下面就用函数来改进。
普通函数版本:
def outer(f,*args): start_time = time.time() ret = f(*args) print(f.__name__ + '('+ str(*args) + ') 运行时间:',time.time() - start_time) return ret print(outer(isPrimes1,32212254719))print(outer(isPrimes2,32212254719))
虽然解决了冗余问题,但是还得去修改N多地方,而且函数的调用方式也改变了,这样还是有风险的,还是在做重复的事情。那还有没有优化的空间呢?在前面有学习过Python中的闭包,即能够保存外层变量的状态,下面就用闭包来改造它:
闭包版本:
def outer(f): def inner(*args): start_time = time.time() ret = f(*args) # f对inner来说,属于外层变量,在inner中能够访问它 print(f.__name__ + '('+ str(*args) + ') 运行时间:',time.time() - start_time) return ret return inner isPrimes1 = outer(isPrimes1)isPrimes2 = outer(isPrimes2) isPrimes1(32212254719)isPrimes2(32212254719)
上面这个版本其实就是我们今天要学习的装饰器函数,它既不改变被装饰函数的调用方式,也不改变被装饰函数中内部的代码,只是给被装饰函数添加了额外的功能。
但是上面的版本,还是不支持关键字参数传递。
闭包版本2:
def outer(f): def inner(*args,**kwargs): start_time = time.time() ret = f(*args,**kwargs) # f对inner来说,属于外层变量,在inner中能够访问它 print(f"{f.__name__}运行时间:{time.time() - start_time} ") return ret return inner isPrimes1 = outer(isPrimes1)isPrimes2 = outer(isPrimes2) isPrimes1(n=32212254719)isPrimes2(32212254719)
这个就是函数装饰器,哪个函数需要装饰,只要在被装饰函数定义后面调用装饰器outer函数,把被装饰函数重新绑定到outer函数的返回对象;目前这一操作我们是通过自己手工添加代码实现的,在Python中提供了装饰器字符: "@"符号,在函数定义前面使用@outer,Python会自动帮我们完成手工赋值的代码。完整代码如下:
import timefrom math import sqrt def outer(f): def inner(*args,**kwargs): start_time = time.time() ret = f(*args,**kwargs) # f对inner来说,属于外层变量,在inner中能够访问它 print(f"{f.__name__}运行时间:{time.time() - start_time} ") return ret return inner @outer # 相当于isPrimes1 = outer(isPrimes1),这个赋值python会自动完成,不需要我们自己赋值def isPrimes1(n): if n return False for i in range(2, int(sqrt(n) + 1)): if n % i == 0: return False return True @outer # 相当于isPrimes2 = outer(isPrimes2),这个赋值python会自动完成,不需要我们自己赋值def isPrimes2(n): if n > 1: if n == 2: return True if n % 2 == 0: return False for x in range(3, int(sqrt(n) + 1), 2): if n % x == 0: return False return True return False # isPrimes1 = outer(isPrimes1)# isPrimes2 = outer(isPrimes2) isPrimes1(n=32212254719)isPrimes2(32212254719)
2:带参数的装饰器
当我们需要关闭时间统计的时候,我们可以把@outer注释掉,也可以给outer传递参数来实现关闭:
import timefrom math import sqrt def outer_o(flag): def outer(f): def inner(*args,**kwargs): if flag: start_time = time.time() ret = f(*args,**kwargs) # f对inner来说,属于外层变量,在inner中能够访问它 if flag: print(f"{f.__name__}运行时间:{time.time() - start_time} ") return ret return inner return outer # 相当于isPrimes1 = outer_o(True) (isPrimes1),这个赋值python会自动完成,不需要我们自己赋值# 注意是@outer_o(True) 而不是@outer_o;outer_o(True)会返回outer函数对象,由于是闭包,最里面的inner函数能够访问它外面的变量;@outer_o(True) def isPrimes1(n): if n return False for i in range(2, int(sqrt(n) + 1)): if n % i == 0: return False return True @outer_o(True) # 相当于isPrimes2 = outer_o(True)(isPrimes2),这个赋值python会自动完成,不需要我们自己赋值def isPrimes2(n): if n > 1: if n == 2: return True if n % 2 == 0: return False for x in range(3, int(sqrt(n) + 1), 2): if n % x == 0: return False return True return False # isPrimes1 = outer(isPrimes1)# isPrimes2 = outer(isPrimes2) print(isPrimes1(32212254719))print(isPrimes2(32212254719)
当关闭时间统计的时候,我们可以把@outer_o(True) 修改为@outer_o(False)
3:多个装饰器
函数可以有多个装饰器,代码如下:
def outer_1(f): def inner(*args,**kwargs): print('in outer_1 start') ret = f(*args,**kwargs) print('in outer_1 end') return ret return inner def outer_2(f): def inner(*args,**kwargs): print('in outer_2 start') ret = f(*args,**kwargs) print('in outer_2 end') return ret return inner # ......"""# 装饰从最靠近函数定义的地方开始,outer_2 首先对原始函数f1进行装饰,outer_1 对outer_2装饰之后返回的结果进行装饰;装饰器执行顺序:最靠近函数定义的地方最后执行;"""@outer_1 # f1 = outer_1(outer_2(f1) )@outer_2 # f1 = outer_2(f1)def f1(a): print('in f1 ',a) f1(100) '''输出结果:in outer_1 startin outer_2 startin f1 100in outer_2 endin outer_1 end'''
除了函数装饰器,还有类装饰器,类装饰器在后面介绍。
领取专属 10元无门槛券
私享最新 技术干货