迭代器和生成器
一、迭代器
迭代是一个重复的过程,迭代器就是每次重复都是基于上次结果而继续的。这里要记住,单纯的重复并不是迭代器。迭代器主要是用来取值的。
列表元祖等是可以利用索引进行取值,但是字典和集合是无序的,我们没有办法根据索引进行取值,要想取去字典的值就必须得用到一种不跟据索引取值的方法,这种方法就是迭代器。
1.1 可迭代对象
要研究迭代器,首先需要弄清楚什么是可迭代对象,在我们学过的数据类型中,有那么几个数据类型是可以被for循环的,而for循环我们之前说过,他就是对可迭代对象的循环。这就说明哪些数据类型是属于可迭代对象的。比如:字符串、列表、字典、集合、元祖。当然我们学的文件对象也是一种可迭代对象。
可迭代对象就是在内置的方法中有__iter__的对象都是可迭代对象。
# 可迭代对象
s = "字符串"
print(s.__iter__()) # 含有__iter__的内置方法就是可迭代对象
1.2 迭代器对象
那么什么又是迭代器对象呢?内置有_iter_、__next__的方法的对象都是迭代器对象,迭代器对象可以通过可迭代对象得到。
# 可迭代对象转为迭代器对象的方法
s = "字符串"
s_iter = s.__iter__() # 此时的s_iter含有__next__的内置方法,就转为了迭代器对象。
# __next__:从头一次取出迭代器对象中的值。
print(s_iter.__next__())
print(s_iter.__next__())
print(s_iter.__next__())
# 如果迭代器对象中的值被取完之后依然继续取,就会报错。
迭代器对象的值一旦被取完,就会“死亡”,不能再进行取值了,在想重新取值的话,就必须重新在赋值然后重新取。
1.3 可迭代对象和迭代器区别
从定义上我们就可以看出,所有的迭代器对象都是可迭代对象,但是不是所有的可迭代对象都是迭代器对象。可迭代对象用iter之后会转化为迭代器对象,迭代器对象用iter转化依然是迭代器对象本身。
1.4 for循环的作用机制
这时候我们就可以了解一下for循环的工作机理了。
# while循环取字典的值。
dic = {"1":2,"2":2}
dic_iter = dic_iter.__iter__()
while True:
try:
print(dic_iter.__next__())
except StopIteration:
break
可以看出来,while循环也可以取出字典的值,但是比较麻烦,我们经常用的for循环更加的方便,那么for循环又是怎么工作的呢?
for k in dic:
print(k)
# 以上述为例。
# 第一步:首先会进行dic = dic.__iter__(),将可迭代对象转化为迭代器对象
# 第二步:进行dic.__next__的调用,得到返回值给k,然后进行代码块的操作
# 第三步:循环第二步,直到出现StopIteration错误,对错误进行捕捉,退出循环。
也就是说实际上for循环就是迭代器循环。
1.5 迭代器的优点和缺点
首先说一下优点:
-
为序列和非序列提供了一个统一的迭代取值的方式。
-
惰性计算:不管迭代器对象有多大,同一时刻只有一行数据存在。
缺点也有两点:
-
在取得时候我们并不知道这个迭代器的长度。
-
取值是一次性的,过去的就让它过去,永远无法回来,除非我们在定义一个新的迭代器对象。
二、生成器
大白话:生成器就是自定义的迭代器。
生成器本身就含有iter和next的内置方法,它本身就是迭代器,那么怎么定义一个生成器呢?那就需要用到yield关键字了,yiled有以下作用
- yield可以暂停函数的运行,不像return,可以让函数处于运行状态且不执行代码。
- yield可以返回值,类似于return,其值就是生成器对象。
# next()的效果和.__next__()是一样的。
# 当生成器遇到next()的调用开始运行,遇到yield停止执行代码,返回生成器对象,等待下次next。
def func():
print(11111)
yield 11111
print(22222)
yield 22222
func() # 此刻这个调用方式已经不好使了
a = func() # 先弄一个生成器出来
b = next(a) # 开始执行代码打印1111,在yield处暂停执行,返回11111
c = next(a) # 继续执行2222,在yield暂停执行,返回22222
d = next(a) # 函数体代码执行完毕,没有返回值,抛出StopIteration异常结束。
TIPS:
如若不先根据函数造一个生成器对象,即a = func()。一直使用next(func())就是在使用一个新的生成器,永远只执行第一个yield。我懵逼在这一段时间,如果你懂请忽略。
yield不止可以返回值,他还可以从外界接受值。
def foo():
print("生成器开始运行了。")
while True:
x = yield 123 # yield表达式
print(f"{x}在运行着")
g = foo() # 创造一个生成器对象
g.send(None) # 等同于next(g) 第一步必须向生成器传一个空值启动生成器。(必须传)
a = g.send(5) # 将5给变量值x,然后开始运行,直到下一个yield,然后返回yield之后的值
在这时,yield一共分三步走,接受值给变量,运行程序,遇到下一个yield返回值。