迭代器和生成器是很重要的概念.
这部分以前自己看书的时候不是很明白,这次来一鼓作气搞懂吧.
举问路的例子:向A问路,A说问B,B说去问C,C把答案告诉B,B把答案告诉A,A告诉问路者,这是迭代,依靠上一层返回结果. 就是递归.
如果问路的时候,向A问路,A说我不知道,你去问B,B说我不知道,你去问C,C直接把答案告诉问路者,这个叫做迭代.
递归:自己调用自己,用调用的自己去获得结果,然后一层一层返回,注意每次调用的自己是新的自己,并不是原来那个自己.
迭代:本来的自己执行了很多次工作,去直接获得结果.
迭代器协议
对象必须提供一个next方法(或者__next__()方法),执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代(只能往后走,不能往前走).
可迭代对象:实现了迭代器协议的对象.例如列表字典等数据类型本身并不是迭代器,实现的方法是对象内定义一个__iter__()方法.通过调用该方法生成可迭代对象.
协议是一种约定,可迭代对象实现了迭代器协议,python的内置函数和其他很多东西,遵循迭代器协议访问可迭代对象.
访问迭代对象,就是在用__iter__()和next()方法来访问对象.
for循环
for 循环是如何遍历迭代器的呢.
字符串,元组,列表,字典,集合,文件对象等,都不是可迭代对象,因为不符合可迭代协议(没有定义next方法).在for 它们的时候,调用了他们内部的__iter__()方法,把它们变成了可迭代对象.这个可迭代对象里有__next__()方法,for会去调用__next__()方法,然后捕捉StopIteration异常,以终止迭代.
a = '321958'
for i in dir(a):
print(i) # 查看字符串的方法,发现有__iter__()
a_iter = a.__iter__() # 执行__iter__()方法获取迭代器
print(a_iter.__next__()) # 返回3
print(a_iter.__next__()) # 返回2
print(a_iter.__next__()) # 返回1
print(a_iter.__next__()) # 返回9
print(a_iter.__next__()) # 返回5
print(a_iter.__next__()) # 返回8
print(a_iter.__next__()) # StopIteration 错误,如果用for, 会被捕捉
for i in a_iter: # 与 for i in a 是一样的
print(i)
for对所有对象的处理本质上都是这样的.不仅序列是这样的,非序列对象如字典,也是用iter方法转换成一个可迭代器来转换.所以对于不能取下标的类型,for也一样能够处理.
内置函数 next()就是调用参数的__next__()方法.
迭代器,就是可迭代对象,这里不要迷糊,二者就是同一个东西.把一个东西变成可迭代对象,就是内部有一个__iter__()方法.
以列表来说,列表不是可迭代对象,对列表进行迭代操作的时候,实际上列表通过__iter__()方法变成了可迭代对象,支持迭代操作.
生成器
迭代器=可迭代对象是一个广义的类,只要一个东西看起来像迭代器,用起来像迭代器,行为也像迭代器,那么就是一个迭代器.
生成器就是符合迭代器这个要求的一种数据类型,但不是迭代器的子类.
生成器是一种数据类型,这个数据类型本身就实现了迭代器协议,即本身就有__next__()方法,不像其他那些数据类型,还需要通过__iter__()才能生成可迭代对象.
生成器在python中有两种表现形式:
- 生成器函数
- 生成器表达式
生成器函数
采取常规函数定义,使用yield语句而不是return语句返回结果,yield语句一次返回一个结果,可以连续使用多个.在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行.
# 多次使用yield的函数
def test():
yield 1
yield 2
yield 3
yield 4
以后看到迭代就要想到生成器函数,看到列表就要想到列表解析式,比如生成一个大写字母的列表a= [chr(x) for x in range(65,91)],生成大写字母的生成器供取用
在使用生成器表达式的时候,先要知道三元表达式和列表解析式
三元表达式与列表解析式
var = value1 if condition else value2
就是将判断条件写在一行里,很python的写法,注意常用.
列表解析式就是用列表符号[]来框住三元表达式(可以二元,但不能超过三个元),可以直接得到一个列表,非常python的写法,遇到列表就要想到列表解析式,特别需要多练.可以一行生成列表.
生成器表达式
列表解析式一次会将所有结果放进内存,如果是需要迭代列表,则可以将列表解析式的[]改成(),就是生成器表达式,这个对象就是一个生成器,可以逐个取出其中的元素.
生成器比列表要节省空间,对于可以用生成器做参数的函数,传入可以变为迭代器的列表,不如直接传入生成器=迭代器.
a = sum(x**2 for x in range(10))
生成器终于理解了,就是一个迭代器对象,按需返回一个一个数值.所占内存空间比一次性传入所有数据要少.
生成器的好处是延迟计算,一次返回一个结果,对于大批量数据处理非常有用(将大批量数据封装在生成器内,一个一个提供出去).
生成器还能够显著提供代码的可读性,缩短代码长度,易于理解逻辑.生成器是很pythonic的写法.
迭代器的方法补充
send(value)方法:
除了for循环和__next__方法之外,send也是触发迭代器工作的一个方法. yield 的特性之一是控制函数的返回值,特性之二就是可以接受send来的值,然后交给引用yield的变量. 在迭代器第一次操作的时候,不能传入None以外的值,因为还没有上一个yield. 第二次之后,每一次send触发生成器的时候,会同时将value传给停在那一行的那个yield(已经触发的yield),然后去触发下一个yield. send函数配合将yield赋给变量,可以实现外界和生成器交互.这里有一篇文章讲了send方法.
#用单线程模拟并发,生产者与消费者模型,实际上是两个程序之间传递消息并处理.
import time
def consumer(name):
while True:
good = yield
time.sleep(0.3)
if good == '最后一个':
print(""{}吃了最后一个"".format(name))
print('{}吃完了{},等待下一个'.format(name,good))
def producer():
i=0
while i<10:
time.sleep(0.2)
consumer1.send(""第{}个东西"".format(i))
i=i+1
print('生产者制造了所有东西')
consumer1.send('最后一个')
return
consumer1 = consumer('jenny')
consumer1.__next__()
producer()