测开之数据类型· 第4篇《迭代器、生成器》
坚持原创输出,点击蓝字关注我吧
作者:清菡
博客:oschina、云+社区、知乎等各大平台都有。
由于微信公众号推送改为了信息流的形式,防止走丢,请给加个星标 ⭐,你就可以第一时间接收到本公众号的推送!
文章总览图
目录
- 一、迭代器
- 1.迭代协议
- 2.什么是迭代器呢?
- 3.可迭代对象
- 4.这个是可迭代对象和迭代器的区别
- 二、生成器
- 1.什么是迭代操作?
- 2.生成器和迭代器有什么不同呢?
- 3.生成器比迭代器多了 3 种方法
- 4.为什么生成器有的方法,迭代器没有?
- 5.数据发送到生成器,在哪个地方呢?
- 三、系列推荐
一、迭代器
1.迭代协议
一种是包含iter
方法的,另一种是包含getitem
方法的(比如str
对象就没有iter
方法,但是一样能够迭代),只要对象中包含了这两种方法的任意一种,那么这个对象就可以进行迭代操作,也就是实现了迭代协议。
2.什么是迭代器呢?
生成器是迭代器的一种。迭代器的范围比生成器更广。只要可以通过next()
,从里面一个一个往外面取值,都被称为迭代器。
关于要创建一个迭代器对象,那么内部要实现一个迭代器的协议。
2.1 迭代器协议
- 实现了迭代器协议的对象(实现方式:对象内部定义了一个
iter()
方法)。 - 对象实现了
__next__
方法。 -
__next__
方法返回了某个数值(当然一般情况下,我们需要的是返回这个对象的特定的数字,并且按照一定的顺序进行依次返回)。 -
__next__
方法需要在值取完的时候,抛出stopiteration
的错误信息。
3.可迭代对象
有个东西需要区分,一个是迭代器,一个是可迭代对象。
只要内部实现了迭代协议的就是一个可迭代对象(可迭代对象可以进行相关的迭代操作,比如for
循环,map
函数等)。
可以用 for 循环进行遍历的,那么都是可迭代对象。可迭代对象不一定是迭代器,迭代器是在可迭代基础上,它内部要首先定义一个__next_
方法。
迭代器内部实现了一个__next_
方法,实现了这个方法之后,通过__next_
这个函数才可以对这个迭代器进行一个取值。
还有个iter()
方法,这个方法可将可迭代对象转换成一个迭代器。
yield
和return
是 2 个东西。yield
只是暂停那个生成器函数。yield
可以从生成器里面生成一个内容。
列表可以进行 for 循环,可以进行 for 循环遍历,它就是个可迭代对象。 列表是可以通过 for 循环遍历的,但是它不是迭代器。
迭代器是可以通过next()
进行取值的。
生成器也是迭代器,生成器是可以通过next()
去取值。那么,生成器它是迭代器的一种,是属于迭代器的。
你看,报错了:
# 列表
# 可迭代对象:可以for循环遍历的都是可迭代对象
li = [1,2,3,4]
next(li)
print(next(li))
提示:列表它不是一个迭代器。
不是个迭代器,不能通过这个去取值。要把一个可迭代对象转换成一个迭代器的话,通过iter()
这个函数把可迭代对象放进去,它能够返回一个迭代器。
你看,这样就能获取到了:
# 列表
# 可迭代对象:可以for循环遍历的都是可迭代对象
li = [1,2,3,4]
li1 = iter(li)
print(next(li1))
print(next(li1))
通过iter()
这个函数,来处理某个对象,它实际上相当于触发这个对象内部的一个__iter__
这个方法。
咱们看看list()
的源码:
通过iter()
这个函数把对象li
传进去的时候,它会触发li
这个对象对应的__iter_
这个方法。
如果通过next()
去取值,把li1
这个对象传进去的时候,实际上是触发这个对象的__next__
方法。
它的类里面只有这个__iter__
方法。
迭代器可以通过__next__
取值。迭代器内部实现了__next__
方法。
迭代器内部实现了 __iter__
方法之外,还实现了__next__
方法。
4.这个是可迭代对象和迭代器的区别
二、生成器
生成器是迭代器的一种。
迭代器是在可迭代对象的基础上实现了__iter_
方法。迭代器和生成器都可以支持迭代操作。
1.什么是迭代操作?
for 循环。
2.生成器和迭代器有什么不同呢?
生成器是迭代器的一种。 刚才用起来的时候好像没有什么区别,打印下这个类型看看。
可以看到,它返回的是个列表迭代器对象:
这个是生成器对象:
li1 = iter(li)
这个是可迭代对象。然后通过iter()
转换成一个迭代器。
3.生成器比迭代器多了 3 种方法
send()方法 | 发送数据 |
---|---|
close() 方法 | 关闭生成器 |
throw() 方法 | 使用的 throw 指令抛出错误 |
生成器是有send
这个方法的,迭代器是没有的。
例如,前面有个生成器叫做tu
:
# () 生成器表达式
tu = (i for i in range(1000))#生成器对象
print(tu)
tu
可以调用send()
这个方法,可以与生成器进行交互,可将数据传输到生成器里面。
4.为什么生成器有的方法,迭代器没有?
举个栗子:
生成器是迭代器的一种。
例如定义了一个父类,再有个子类,父类创建出一个对象,子类创建出一个对象。子类有自己的方法。父类创建的出来的对象里面,肯定没有子类对象里面的方法。 子类里面有的方法,父类里面没有。
迭代器就是“父类”。生成器就是“子类”。
def gen():
for i in range(1,5):
yield i
gen()
生成器运行的时候,调用函数gen()
,调用这个函数的时候,这个函数里面的代码不会直接运行。
代码修改成这样:
def gen():
for i in range(1,5):
yield i
g = gen()
print(g)
只有通过next()
方法往生成器里面取值的时候,它才会从代码上面往下面运行。
这个send()
方法可将数据传到生成器里面。使用next()
,从生成器里面获取出一个值。如果使用send()
方法,它也能够获取出来一条数据。
def gen():
for i in range(1,5):
se = yield i
print(se)
g = gen()
print(next(g))
print(g.send(100))
send()
方法可以往生成器里面传入一个值。
通过send()
方法生成数据的时候,它也可以往里面发送一个 100 的值。
5.数据发送到生成器,在哪个地方呢?
如果通过next()
去取值的话,这个yield
完毕后是没有返回内容的。
代码详解:
第一轮: 循环进来,通过next()
去取值生成了一个 1:
def gen():
for i in range(1,5):
se = yield i
print(se)
g = gen()
print(next(g))
第二轮: 通过print(g.send(100))
去发送值,然后打印:
def gen():
for i in range(1,5):
se = yield i
print('se的值:',se)
g = gen()
print(next(g))
print(g.send(100))
在第一轮结束之后,在yield
这里,yield
完毕就停止了。在第一轮yield
完之后,第二轮通过send()
传值进去,传到se
那里,打印出来 100。
然后再往上返回一个数据,又暂停,返回第二条数据就是个 2。
第三轮: 通过next()
再去生成一条元素,又触发了yield i
这个地方,这里释放了,往后面走,往后面走的话,但是没有放数据进来,这个时候se
是空的,打印出来的se
是空的。
然后再往上,生成一条元素到 3,然后又停在yield i
这个地方了,生成完元素,把这个值返回出去。
def gen():
for i in range(1,5):
se = yield i
print('se的值:',se)
g = gen()
print(next(g))
print(g.send(100))
print(next(g))
再次next()
或者send()
来触发它的时候,它会这样走:
注意: yield
接收不是存在i
中,这个yield
返回出来的i
是遍历出来的内容。
send()
发进去的,是yield i
这里运行完毕之后,当下一个send()
触发的时候,它把这个值发送到yield i
这里运行完毕之后的一个结果。
yield i
这里把这个i
返回出去,就停在这里不动了。send()
发送个数据进去,那么数据就发送到个yield i
这地方。
相当于yield i
这个地方返回的一个结果,也就是send()
发进去的内容,如果send()
不发进去内容,返回出来是个空的。
温馨提示:生成器<迭代器<可迭代对象
三、系列推荐
- 测开入门篇《环境管理、编码规范、项目结构》
- 数据类型· 第 1 篇《元组和列表的性能分析、命名元组》
- 数据类型第 2 篇「字典和集合的原理和应用」
- 测开之数据类型· 第 3 篇《列表推导式、字典推导式、2 种方式创建生成器》
- 《Web 自动化》基础知识脑图
公众号 「清菡软件测试」 首发,更多原创文章:清菡软件测试 109+原创文章,欢迎关注、交流,禁止第三方擅自转载。