迭代器和生成器详解
迭代器和生成器
一、迭代器
迭代是一个重复的过程,迭代器就是每次重复都是基于上次结果而继续的。这里要记住,单纯的重复并不是迭代器。迭代器主要是用来取值的。
列表元祖等是可以利用索引进行取值,但是字典和集合是无序的,我们没有办法根据索引进行取值,要想取去字典的值就必须得用到一种不跟据索引取值的方法,这种方法就是迭代器。
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返回值。
什么是生成器
通过列表推导式,可以直接创建一个列表,但是收到内存限制,列表容量肯定是有限的而且,创建一个包含100万个元素的列表,占用很大的存储空间。如果我们仅仅需要访问前面几个元素,后面 后面元素的占用存储空间就被浪费
所以,如果列表元素可以按照某种算法算出来,那我们就可以再循环当中不断地推导它,生成元素,这样就不必创建完整的list,从而大大节省了存储空间。
在Python中,这种一边循环一边计算的机制,成为生成器:generator
斐波那契数列案例实现生成器:
#数列写法:1,1,2,3,5,8
#函数写法
def fib(times):
a = 0
b = 1
n = 1
while n<=times:
print(b)
a,b = b,a+b
n += 1
fib(3)
#生成器写法
def fib(times):
a = 0
b = 1
n = 1
while n<=times:
yield b
a,b = b,a+b
n+=1
p = fib(5)
print(p)
执行的结果为:
1 #函数写法执行的结果
1 #函数写法执行的结果
2 #函数写法执行的结果
<generator object fib at 0x0000000002174468> #生成器写法执行的结果
yield 的作用就是把一个函数变成一个生成器, 带有yield的函数不再是一个普通的函数。python解释器会将其视为一个generator
调用fib(5)不会返回fib函数 而是返回一个迭代器对象,在for循环执行时,每次循环都会执行fib函数的内部代码
什么是迭代器
迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一 个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
可迭代对象
以直接作用于 for 循环的数据类型有以下几种:
一类是集合数据类型,如 list 、 tuple 、 dict 、 set 、 str 等;
一类是 generator ,包括生成器和带 yield 的generator function。这些可以直接作用于 for 循环的对象统称为可迭代对象:Iterable 。
判断是否是可迭代
可以使用 isinstance() 判断一个对象是否是 Iterable 对象:
而生成器不但可以作用于 for 循环,还可以被 next() 函数不断调用并返回下一个值,直到最后抛出
StopIteration 错误表示无法继续返回下一个值了。
迭代器
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。可以使用 isinstance() 判断一个对象是否是 Iterator 对象:
iter函数
生成器都是 Iterator 对象,但 list 、 dict 、 str 虽然是 Iterable ,却不是 Iterator 。把 list 、 dict 、 str 等 Iterable 变成 Iterator 可以使用 iter() 函数:
总结:
迭代器不一定是生成器,生成器一定是迭代器
凡是可作用于 for 循环的对象都是 Iterable 类型; 凡是可作用于 next() 函数的对象都是 Iterator 类型
集合数据类型如 list 、 dict 、 str 等是 Iterable 但不是 Iterator ,不过可以通过 iter() 函数获得一个Iterator 对象。