深入剖析三兄弟:可迭代对象,迭代器和生成器
可迭代对象
我们在用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__ 方法会得到一个生成器对象。
生成器表达式是创建生成器的简洁句法,这样无需先定义函数再调用。不过,生成器函数灵活得多,可以使用多个语句实现复杂的逻辑,也可以作为协程使用,遇到简单的情况时,可以使用生成器表达式,因为这样扫一眼就知道代码的作用。
- 点赞
- 收藏
- 关注作者
评论(0)