深入剖析三兄弟:可迭代对象,迭代器和生成器

举报
子都爱学习 发表于 2021/04/26 19:58:51 2021/04/26
【摘要】 可迭代对象我们在用for ... in ...语句循环时,in后面跟随的对象要求是可迭代对象,即可以直接作用于for循环的对象统称为可迭代对象(Iterable),如list、tuple、dict、set、str等一个序列可以被for语句循环遍历的原因是实现了 __iter__  的方法,或者实现了 __getitem__ 的方法且参数是从0开始的整数,即就认为对象是可迭代的。解释器需要迭代...

可迭代对象
我们在用for ... in ...语句循环时,in后面跟随的对象要求是可迭代对象,即可以直接作用于for循环的对象统称为可迭代对象(Iterable),如list、tuple、dict、set、str等
一个序列可以被for语句循环遍历的原因是实现了 __iter__  的方法,或者实现了 __getitem__ 的方法且参数是从0开始的整数,即就认为对象是可迭代的。

解释器需要迭代对象 x 时,会自动调用 iter()。
内置的 iter() 函数执行过程:
(1)检查对象是否实现了 __iter__ 方法,没有会去检查是否实现了 __getitem__ 方法,如果有实现了就调用它,获取一个迭代器。
(2)尝试失败,抛出“对象不可迭代”错误

>>> s = 'demon'
>>> for char in s:
...     print(char)
... 
d
e
m
o
n

如果没有 for 语句,不得不使用 while 循环模拟,要像下面这样写:

1 s = 'demon'
2 it = iter(s)                        #使用迭代器迭代s字符串
3 
4 while True:
5     try:
6         print(next(it))             #调用next等通过从iter中获取值
7     except StopIteration:           #当迭代器中的所有数据都迭代完毕以后,会报StopIteration的异常
8         del it
9         break                       #删除迭代器并跳出循环体

迭代器
标准的迭代器接口有两个方法。
__next__
返回下一个可用的元素,如果没有元素了,抛出 StopIteration异常。
__iter__
这个接口在 collections.abc.Iterator 抽象基类中制定。这个类定义了 __next__ 抽象方法,而且继承自 Iterable 类;__iter__ 抽象方法则在 Iterable 类中定义。如图所示。
Iterable 和 Iterator 抽象基类。以斜体显示的是抽象方法。具体的 Iterable.__iter__ 方法应该返回一个 Iterator 实例。具体的 Iterator 类必须实现 __next__ 方法。Iterator.__iter__ 方法直接返回实例本身。

 1 import re
 2 import reprlib
 3 
 4 RE_WORD = re.compile(r'\w+')
 5 
 6 
 7 class Sentence:
 8 
 9     def __init__(self, text):
10         self.text = text
11         self.words = RE_WORD.findall(self.text)
12 
13     def __repr__(self):
14         return 'Sentence(%s)' % reprlib.repr(self.text)
15 
16     def __iter__(self):                         #可以迭代
17         return SentenceIterator(self.words)     #返回一个迭代器
18 
19 
20 class SentenceIterator:
21 
22     def __init__(self, words):                  #SentenceIterator 实例引用单词列表
23         self.words = words
24         self.index = 0                          #self.index 用于确定下一个要获取的单词
25 
26     def __next__(self):
27         try:
28             word = self.words[self.index]       #获取 self.index 索引位上的单词
29         except IndexError:                      #获取的索引上没有值,就代表迭代器闷得蜜了吧~
30             raise StopIteration()               #结束了,当然是抛出StopIteration的异常咯
31         self.index += 1                         #要么就让索引自动+1供下次迭代器调用使用
32         return word                             #返回索引上面的值
33 
34     def __iter__(self):                         #实现__iter__方法
35         return self

先了解可迭代对象与迭代器之间的关系:
Python从可迭代对象中获取迭代器。,迭代器是可迭代的对象。
可迭代的对象一定不能是自身的迭代器。也就是说,可迭代对象必须实现  __iter__  方法,但不能实现  __next__  方法
迭代器应该一直可以迭代。迭代器的 __iter__方法应该返回自身
 
因为迭代器只需 __next__ 和 __iter__ 两个方法,所以除了调用next() 方法,以及捕获 StopIteration 异常之外,没有办法检查是否还有遗留的元素。此外,也没有办法“还原”迭代器。如果想再次迭代,那就要调用 iter(...),传入之前构建迭代器的可迭代对象。传入迭代器本身没用,因为前面说过 Iterator.__iter__ 方法的实现方式是返回实例本身,所以传入迭代器无法还原已经耗尽的迭代器。

ret = iter('123')
while True:
    try:
        print(next(ret))
print(ret)                         #报错: IndentationError: unexpected unindent,原因迭代器迭代完会自动删除实例

总结:迭代器是这样的对象:实现了无参数的 __next__ 方法,返回序列中的下一个元素;如果没有元素了,那么抛出StopIteration异常。Python中的迭代器还实现了 __iter__ 方法,因此迭代器也可以迭代。

生成器
生成器函数的工作原理:普通的函数与生成器函数在句法上唯一的区别是,在后者的定义体中有 yield 关键字。调用生成器函数时,就会返回一个生成器对象。生成器由于内部的惰性实现,不会一次性返回,取值时才会去获取,极大的节省了内存。

 1 def gen_123():
 2     yield 1
 3     yield 2
 4     yield 3
 5 
 6 
 7 print('gen_123是个函数', gen_123)
 8 print('gen_123调用的时候是个生成器', gen_123())
 9 
10 for i in gen_123():
11     print(i)
12 
13 g = gen_123()
14 print('使用next来一发', next(g))
15 print('使用next来二发', next(g))
16 print('使用next来三发', next(g))
17 try:
18     next(g)
19 except StopIteration:
20     print('报错哦,迭代器结束咯~')
21 else:
22     print('啊啊啊啊啊~~官人,好疼呀!')


gen_123是个函数 <function gen_123 at 0x100562e18> gen_123调用的时候是个生成器 <generator object gen_123 at 0x102146308> 1 2 3 使用next来一发 1 使用next来二发 2 使用next来三发 3 报错哦,迭代器里面没有值了~

生成器函数会创建一个生成器对象,包装生成器函数的定义体。把生成器传给 next(...) 函数时,生成器函数会向前,执行函数定义体中的下一个 yield 语句,返回产出的值,并在函数定义体的当前位置暂停。最终,函数的定义体返回时,外层的生成器对象会抛出StopIteration 异常——这一点与迭代器协议一致。

生成器表达式也会创建一个生成器对象。不过,两者最终的效果一样:调用 __iter__ 方法会得到一个生成器对象。
生成器表达式是创建生成器的简洁句法,这样无需先定义函数再调用。不过,生成器函数灵活得多,可以使用多个语句实现复杂的逻辑,也可以作为协程使用,遇到简单的情况时,可以使用生成器表达式,因为这样扫一眼就知道代码的作用。


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。