1.装饰器
明月装饰了你的窗子,你装饰了我的梦。所谓装饰就是不改变原来实物本身,只是在事物之外给它加以装饰。
在编程里一样,因为项目会一直有优化、更新,所以可能会对以前的功能进行优化,那么开发的原则是“开放-封闭”。开放:允许在原有的功能上扩展功能;封闭:不允许修改原代码。所以有了装饰器,在不改变源代码的情况下,增加功能。
装饰器原理:
# 现在已经写好了一个函数,现在需要得到它的运行时间 import time,random def sort_lis(n): lis = [i for i in range(n)] random.shuffle(lis) #打乱列表顺序 lis.sort() # n越大耗时越多,排序是cpu密集型,消耗cpu时间碎片很多 print("done!") start_time = time.time() sort_lis(1000000) end_time = time.time() rum_time = end_time - start_time print(rum_time) #完美,我们拿到这个时间,但是如果有很多个这样的函数需要获得运行时间,每次调用都这样去加代码显然很low,而且违反了开发项目的封闭原则。
这个时候会思考,那么我把这个获取时间的代码,写成一个函数,调sort_lis的时候,直接调这个函数就好了,但是要怎么写呢,因为sort_lis要在你的函数中运行才能拿到时间啊,于是写出了这样的代码
import time,random def get_run_time(func): start_time = time.time() res = func() end_time = time.time() rum_time = end_time - start_time print(rum_time) return res def sort_lis(): lis = [i for i in range(100000)] random.shuffle(lis) #打乱列表顺序 lis.sort() # n越大耗时越多,排序是cpu密集型,消耗cpu时间碎片很多 print("done!") get_run_time(sort_lis)
哈哈哈,太机智了,直接就拿到了这个运行时间,但是老铁,1你改变了函数的调用方式,2.有没发现万一sort_lis这个函数有传参怎么办,再这样写是不是凉凉了?于是想到了更进一步的办法
import time,random def get_run_time(func): def wrapper(): start_time = time.time() res = func() end_time = time.time() rum_time = end_time - start_time print(rum_time) return res return wrapper def sort_lis(): lis = [i for i in range(10000)] random.shuffle(lis) #打乱列表顺序 lis.sort() # n越大耗时越多,排序是cpu密集型,消耗cpu时间碎片很多 print("done!") new_func = get_run_time(sort_lis) new_func()
这回你想没啥问题了吧,但是还是上面两点没达到,别人之前是sort_lis()调用,现在变成了new_func()调用,函数名都被你改了,以前的那么多代码都一个个去改么,显然又懵了,于是你想到了我直接用原函数变量名来命名不就好了么?
import time,random def get_run_time(func): def wrapper(): start_time = time.time() res = func() end_time = time.time() rum_time = end_time - start_time print(rum_time) return res return wrapper def sort_lis(): lis = [i for i in range(10000)] random.shuffle(lis) #打乱列表顺序 lis.sort() # n越大耗时越多,排序是cpu密集型,消耗cpu时间碎片很多 print("done!") sort_lis = get_run_time(sort_lis) sort_lis()
看这样我就实现了吧,既不改变原函数代码,也没改变原来的调用方式,只是加了一句 sort_lis = get_run_time(sort_lis) 就搞定了,没错,这就是装饰器的原理了,不过一般不这样写,用@,俗称语法糖,如下:
import time,random def get_run_time(func): def wrapper(): start_time = time.time() res = func() end_time = time.time() rum_time = end_time - start_time print(rum_time) return res return wrapper @get_run_time def sort_lis(): lis = [i for i in range(10000)] random.shuffle(lis) #打乱列表顺序 lis.sort() # n越大耗时越多,排序是cpu密集型,消耗cpu时间碎片很多 print("done!") sort_lis()
那么问题来了,要是被装饰函数有传参,上面的这个不是搞不定了吗?
被装饰函数有传参:
import time,random def get_run_time(func): def wrapper(*args,**kwargs): start_time = time.time() res = func(*args,**kwargs) end_time = time.time() rum_time = end_time - start_time print(rum_time) return res return wrapper @get_run_time def sort_lis(n): lis = [i for i in range(n)] random.shuffle(lis) #打乱列表顺序 lis.sort() # n越大耗时越多,排序是cpu密集型,消耗cpu时间碎片很多 print("done!") sort_lis(10000)
装饰器本身带有传参:等于是把上面的装饰器再包了一层。
比如这里你想让测试人员吓一跳,你可以这样弄,一个功能相应时间多加sec秒,然后下次提测把不传这个参数,相应时间瞬间就上去了,让测试觉得你很厉害,嘿嘿。
import time,random def timer(sec=''): def get_run_time(func): def wrapper(*args, **kwargs): start_time = time.time() if sec: time.sleep(sec) res = func(*args, **kwargs) end_time = time.time() rum_time = end_time - start_time print(rum_time) return res return wrapper return get_run_time @timer(2) def sort_lis(n): lis = [i for i in range(n)] random.shuffle(lis) #打乱列表顺序 lis.sort() # n越大耗时越多,排序是cpu密集型,消耗cpu时间碎片很多 print("done!") sort_lis(10000)
2.生成器
先来看下列表生成式:就是根据一个规则,生成一个列表。
lis = [i for i in range(10)] new_lis = [x**x for x in lis] print(lis,new_lis)
生成式很好用,但是当列表元素太多的时候,内存会吃不消,有没有一种方法,我只做一个生产元素的模型,需要元素了我再去模型里拿,每取一次给我生成一个元素,这样就不占内存,而且还达到了目的。是的,这个模型就是生成器。
生成器的创建和调用:
# 生成器的第一种创建方式:就是把上面的列表生成式的[]符号,改成(),就是一个生成器了: new_lis = (x**x for x in range(10)) print(new_lis) # <generator object <genexpr> at 0x0000000004034938> 这就是我们的生成器了 # 生成器的第二种创建方式:函数里加上 yield,函数就变成了一个生成器 def fib(max): n, a, b = 0, 0, 1 while n < max: # print(b) yield b a, b = b, a + b n = n + 1 return 'done' new_fib = fib(2) print(new_fib) # <generator object fib at 0x00000000040149E8> 这就是我们函数生成的生成器了 # 生成器的三种调用方式: print(next(new_fib)) # 直接next可以一次次的获取生成器生成的元素,当取完最后一个元素时,会报错:StopIteration: done print(new_fib.send("hello")) # 同next,只是可以传递一个信号到生成器内部。 for i in new_fib: # 这个调用和循环去可迭代对象一样,一般是用这种方法调用生成器,调完结束不会报错。 print(i)
上面有说道信号,这个信号有啥用呢?
def fib2(max): n, a, b = 0, 0, 1 while n < max: # print(b) single = yield b #拿到send过来的信号 if single == "stop": #如果信号说stop,就跳出循环,不再生成元素 break a, b = b, a + b n = n + 1 return 'done' new_fib = fib2(7) print(next(new_fib)) print(new_fib.send("hello")) print(new_fib.send("stop")) #生成器在接到这个信号的时候,直接不再返回元素,抛错:StopIteration: done
3.迭代器
我们已经知道,可以直接作用于for循环的数据类型有以下几种:一类是集合数据类型,如list、tuple、dict、set、str等;一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。可以使用isinstance()判断一个对象是否是Iterable对象:
from collections import Iterable isinstance([], Iterable) # True isinstance({}, Iterable) # True isinstance('abc', Iterable) # True isinstance((x for x in range(10)), Iterable) # True isinstance(100, Iterable) # False
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
*可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。可以使用isinstance()判断一个对象是否是Iterator对象:
from collections import Iterator isinstance((x for x in range(10)), Iterator) # True isinstance([], Iterator) # False isinstance({}, Iterator) # False isinstance('abc', Iterator) # False
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
把list、dict、str等Iterable变成Iterator可以使用iter()函数:
isinstance(iter([]), Iterator) # True isinstance(iter('abc'), Iterator) # True
可能会有疑问,为什么list、dict、str等数据类型不是Iterator?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
小结:
凡是可作用于for循环的对象都是Iterable类型;
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
Python3的for循环本质上就是通过不断调用next()函数实现的,例如:
for x in [1, 2, 3, 4, 5]: pass # 实际上完全等价于: # 首先获得Iterator对象: it = iter([1, 2, 3, 4, 5]) # 循环: while True: try: # 获得下一个值: x = next(it) except StopIteration: # 遇到StopIteration就退出循环 break