迭代器和生成器详解

迭代器和生成器

 

 

一、迭代器

迭代是一个重复的过程,迭代器就是每次重复都是基于上次结果而继续的。这里要记住,单纯的重复并不是迭代器。迭代器主要是用来取值的。

列表元祖等是可以利用索引进行取值,但是字典和集合是无序的,我们没有办法根据索引进行取值,要想取去字典的值就必须得用到一种不跟据索引取值的方法,这种方法就是迭代器。

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 迭代器的优点和缺点

首先说一下优点:

  1. 为序列和非序列提供了一个统一的迭代取值的方式。

  2. 惰性计算:不管迭代器对象有多大,同一时刻只有一行数据存在。

缺点也有两点:

  1. 在取得时候我们并不知道这个迭代器的长度。

  2. 取值是一次性的,过去的就让它过去,永远无法回来,除非我们在定义一个新的迭代器对象。

二、生成器

大白话:生成器就是自定义的迭代器。

生成器本身就含有iter和next的内置方法,它本身就是迭代器,那么怎么定义一个生成器呢?那就需要用到yield关键字了,yiled有以下作用

  1. yield可以暂停函数的运行,不像return,可以让函数处于运行状态且不执行代码。
  2. 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 对象。

posted @ 2021-09-09 19:53  Jacob高  阅读(1077)  评论(0编辑  收藏  举报