装饰器是python中较为高级的用法,它的使用也是比较不容易理解的。
在最近的学习中,找到一个讲解的很清楚的视频,解决了我多年的疑惑,放在文后。
之前面试的时候也问到了这个知识点,我只能连蒙带猜的胡说了一通,现在学完再回过头一看,我如果是面试官也不会自己过吧,哈哈哈。
那么现在我们就来一起学习装饰器
import time
def is_prime(num):
if num < 2: return False
elif num == 2: return True
else:
for i in range(2,num):
if num % 2 == 0: return False
return True
def prime_nums():
t1 = time.time()
for i in range(2,10000):
if is_prime(i):
print(i)
t2 = time.time()
print(t1,t2,t2-t1)
prime_nums()
首先是一段简单的代码,用来统计0-9999中的质数,没有用到装饰器,通过time模块来统计prime_nums函数运行的时间,运行一遍是9.72s。
函数本身没什么问题,但是我们的prime_nums函数是一个用来统计质数的函数,统计时间的功能在这里是突兀的,我们想要将这个功能移出函数,放在另一个函数中,这就有了第二个程序
import time
def display_time(func):
t1 = time.time()
func()
t2 = time.time()
print(t1,t2,t2-t1)
def is_prime(num):
if num < 2: return False
elif num == 2: return True
else:
for i in range(2,num):
if num % 2 == 0: return False
return True
def prime_nums():
for i in range(2,10000):
if is_prime(i):
print(i)
display_time(prime_nums)
这个程序在原程序的基础上将计时功能抽离了出来,作为display_time函数。这个函数的参数是函数名,python是允许将函数名作为参数进行传递,而不会在传递过程中执行这个函数的,直到出现func()这样的时候才会真正执行它。将功能抽取出来后,用时2.22s,可以发现,节省了许多运行时间。
但是,有个问题,我们最后执行的是display_time(prime_nums),这里有一个主从的关系,看起来似乎主体是显示时间,但实际上显示时间只是一个附加的功能,主要的功能是找到质数。这样写不利于我们阅读程序。
同时如果保持现状,在开发完成后,我们提供给测试者和使用者的将会是display_time()这个功能,这将会带来一些歧义。而我们想提供其实是prime_nums()这样一个功能。因此,得益于python的语法糖,我们可以继续改进:
import time
def display_time(func):
def wrapper():
t1 = time.time()
func()
t2 = time.time()
print(t1,t2,t2-t1)
return wrapper
def is_prime(num):
if num < 2: return False
elif num == 2: return True
else:
for i in range(2,num):
if num % 2 == 0: return False
return True
@display_time
def prime_nums():
for i in range(2,10000):
if is_prime(i):
print(i)
prime_nums()
现在我们的display_time就是一个装饰器了,他会返回一个wrapper的函数名,而这个函数实际上是对func()做了增强,增加了计时的功能。
此时,执行用@display_time装饰了的prime_nums时,实际上是执行了display_time(prime_nums)中的wrapper(),并在wrapper中执行了prime_nums()
所谓面向切面编程,就是将一个复杂的函数分为一个个小的功能,在需要修改时只需要修改一小部分,而不需要对整个的大函数进行修改。
我们的第一个装饰器跑通了,但现还要对他做一些改进:
- 一个是如果要传参,而不是固定10000的时候
- 一个是如果有返回值的时候
函数就需要做一些改进,首先是处理返回值:
import time
def display_time(func):
def wrapper():
t1 = time.time()
result = func()
t2 = time.time()
print(t1,t2,t2-t1)
return result
return wrapper
def is_prime(num):
if num < 2: return False
elif num == 2: return True
else:
for i in range(2,num):
if num % 2 == 0: return False
return True
@display_time
def prime_nums():
count = 0
for i in range(2,10000):
if is_prime(i): count += 1
return count
count = prime_nums()
print(count)
接上文所述,因为实际执行的是wrapper(),所以wrapper()必须有一个返回值,我们将func()的结果用result保存起来,再返回就可以了。
接下来是处理参数:
import time
def display_time(func):
def wrapper(*args,**kwargs):
t1 = time.time()
result = func(*args,**kwargs)
t2 = time.time()
print(t1,t2,t2-t1)
return result
return wrapper
def is_prime(num):
if num < 2: return False
elif num == 2: return True
else:
for i in range(2,num):
if num % 2 == 0: return False
return True
@display_time
def prime_nums(max_num):
count = 0
for i in range(2,max_num):
if is_prime(i): count += 1
return count
count = prime_nums(10000)
print(count)
解决办法就是在wrapper()中将写上参数。本题中其实也可以写wrapper(max_num)和result = func(max_num),但是在处理有很多参数的函数时,使用wrapper(*args,**kwargs)的写法将所有的参数传递过去是非常方便的。
同时,在这里我们可以更加容易的看出来,
@display_time
def prime_nums(max_num):
这样的写法,其实等价于display_time(prime_nums)(max_num)。display_time(prime_nums) == wrapper,最后执行的是wrapper(max_num),只不过wrapper是在display_time中执行,能够调用func = prime_nums而已。
好了,装饰器的基础知识就学习完了,想要深入了解,重要的还是多看,多练,多思考。
大佬的视频在这里,讲得非常不错:https://www.bilibili.com/video/BV11s411V7Dt?from=search&seid=12540511450060276795
网友评论