一.什么是迭代器协议
1,迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么引起一个Stopiteration异常,
以终止迭代(只能往后走,不能往前退)
2,可迭代对象:实现了迭代器协议的对象(如何实现:对象内部定义一个__iter__()方法)
3,协议是一种约定,可迭代对象实现了迭代器协议,python中的内部工具(例如:for循环,sum,min,max函数等)使用迭代器
协议访问对象
二.python中强大的for循环机制
for循环的本质:循环所有对象,全都是使用迭代器协议
(字符串,列表,元组,字典,集合,文件对象)这些都不是可迭代的对象,只不过for在循环式,调用了它们的__iter__方法,
把它们变成了可迭代的对象,然后for循环调用可迭代对象的__next__方法去取值,并且for循环会扑捉到Stopiteration异常,来
终止迭代
1 list_test = [11,22,33] 2 list_iter = list_test.__iter__() 3 print(list_iter.__next__()) # 11 4 print(list_iter.__next__()) # 22 5 print(list_iter.__next__()) # 33 6 print(list_iter.__next__()) # 超出边界就报错StopIteration 7 8 #for循环list_test本质上就是遵循迭代器的访问方式, 9 先调用list_iter = list_test.__iter__()方法,或者直接list_iter = iter(list_test) 10 然后依次执行list_iter.next(),直到for循环捕捉到StopIteration终止循环 11 12 #for循环所有对象的本质都是一样的道理 13 14 #接下来我们来使用while去模拟一下for循环做的事情 15 list_test = [11,22,33] 16 list_iter = list_test.__iter__() 17 while True: 18 try: 19 print(list_iter.__next__()) 20 except StopIteration: 21 print("迭代完成了,循环终止了") 22 break
通过以上上述方式,我们可以看出,for循环就是基于迭代器协议提供了一个统一的可以遍历所有对象的方法,即在遍历之前
先调用对象的__iter__方法将其转换成一个迭代器,然后使用迭代器去实现循环访问,这样所有的对象就都可以通过for循环来
遍历了
三,生成器
生成器不会把结果保存在一个系列中,而是保存在生成器的状态,在每次进行迭代时返回一个值,直到遇到StopIteration异常结束
生成器可以理解为一种数据类型,这种数据类型自动实现了迭代器协议,(其他的数据类型需要调用自己的内置的__iter__方法,
)所以生成器就是可迭代对象
生成器在python中的形式表现:
1,生成器函数:常规函数定义,但是,使用yield语句而不是return语返回结果,yield语句一次返回一个结果,在每个结果中间,
挂起函数当期的状态,以便下次重它(yield)离开的地方继续执行
2,生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
生成器的优点:
python使用生成器对延迟操作提供了支持,所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果
生成器小结:
1,是可迭代对象
2,实现了延迟计算,省内存
3,生成器本质和其他数据类型一样,都是实现了迭代器协议,只不过生成器附加了一个延迟计算省内存的好处,
其余的可迭代对象没有这个功能
四:生成器函数
1 def lay_eggs(num): 2 egg_list = [] 3 for egg in range(num): 4 egg_list.append('蛋%s'%egg) 5 return egg_list 6 7 total = lay_eggs(10) 8 print(total) 9 10 #['蛋0', '蛋1', '蛋2', '蛋3', '蛋4', '蛋5', '蛋6', '蛋7', '蛋8', '蛋9'] 11 12 13 def lay_eggs(num): 14 for egg in range(num): 15 result = '蛋%s' %egg 16 yield result 17 print('下完一个蛋了') 18 19 total = lay_eggs(10) # 我们拿到的是一只老母鸡,可以按照我们的需要进行下蛋 20 print(total) 21 print(total.__next__()) 22 print(total.__next__()) 23 print(total.__next__()) 24 egg_l = list(total) 25 print(egg_l) 26 #演示只能往后不能往前走
五:生成器表达式和列表解析
1 #三元表达式 2 #为真的结果 ---表达式---为假的结果 3 4 name = 'ying' 5 res = 'very good' if name == 'ying' else '不存在' 6 print(res) # very good
列表解析事例
1 #列表解析 2 3 egg_list = ['鸡蛋%s'%i for i in range(10)] 4 print(egg_list) 5 #['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9'] 6 7 8 #生成器表达式 9 egg_list = ('鸡蛋%s' %i for i in range(10)) 10 print(egg_list) 11 print(next(egg_list)) # next本质就是调用__next__ 12 print(egg_list.__next__()) 13 14 15 #<generator object <genexpr> at 0x000000000113C1A8> 16 #鸡蛋0 17 #鸡蛋1
小结:
1,把列表解析的[]换成()得到的就是生成器表达式
2,列表解析与生成器表达式都是一种编程方式,只不过生成器表达式更节省内存空间
3,python不但使用迭代器协议,让for循环变得更加通用,大部分内置函数,也是使用迭代器协议访问对象的
比如,sum函数是python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以
我们可以直接这样来计算一系列值的和
1 print(sum(i for i in range(10))) # 所有数相加的和 2 print(sum(i** 2 for i in range(10))) # i的2次方,285
而不用多此一举的先构建一个列表然后在来计算:
1 sum([i** 2 for i in range(10)])
六:生成器总结
1.生成器的运行机制:一边循环一边计算,每次调用next()方法,就能计算出下一个元素的值,直到计算到最后一个元素,
没有更多的元素时,就抛出StopIteration的错误,在每次调用next()的时候,遇到yield语句就返回,再次执行时从上次返回
的yield语句处继续执行。
2.通过yield关键字定义生成器,如果一个函数中包含yield,那么这个函数就不再是一个普通的函数,而是一个generator,生成器和函数的执行流程不一样,函数的顺序执行,遇到return语句或者最后一行函数语句就结束,而生成器的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上句返回的yield语句处继续执行
下面我们通过yield实现在单线程的情况下实现并发运算的效果
1 import time 2 def consumer(name): 3 print('%s准备吃包子啦!' %name) 4 while True: 5 baozi = yield # 生成器遇到yield就会终止该函数的操作,并且会将函数执行的状态挂起, 6 # 直到send来恢复当前状态,然后继续执行以下的代码 7 print('包子[%s]来啦!,被[%s]吃了!!' %(baozi,name)) 8 def producer(): 9 c = consumer('laiying') # 创建了一个生成器c 10 b = consumer('zcy') # 创建了一个生成器b 11 c.__next__() # 通过c.__next__()执行consumer这个生成器 12 b.__next__() # 通过c.__next__()执行consumer这个生成器 13 print('陈师傅要开始做包子啦!') 14 for i in range(1,10): 15 time.sleep(2) 16 print('做了2个包子') 17 c.send(i) # 通过send方法去恢复yield的挂起状态,并将变量i的值传递给yield 18 b.send(i) #其实__next__方法也可以恢复yield的挂起状态,但是__next__不能传值给yield 19 producer()