可迭代对象、迭代器、生成器
迭代器用于从集合中取出元素,而生成器用于“凭空”产生元素。
- 可迭代对象
- 迭代器
- 生成器
iter
函数
1 什么是可迭代对象?
使用iter
内置函数可以获取迭代器的对象叫可迭代对象。
如果对象实现了能返回迭代器的__iter__
方法,那么对象就是可迭代的;序列都可以迭代;实现了__getitem__
方法,而且其参数是从0开始的索引,这种对象也是可迭代的。Python从可迭代的对象中获取迭代器,且可迭代的对象一定不能是自身的迭代器,即可迭代的对象必须实现__iter__
,但不能实现__next__
。
示例1,实现__getitem__
方法的可迭代对象Word类:
class Word:
"""实现了__getitem__但没有实现__iter__"""
def __init__(self, text):
self.words = text.split(" ")
def __getitem__(self, index):
return self.words[index]
def __len__(self):
return len(self.words)
Word对象实例演示如下:
>>> w = Word("I am a student")
>>> for x in w:#
... print(x)
...
I
am
a
student
示例1演示表明,Word实例可以迭代:实现了__getitem__
方法且下标从0开始的类对象(Word类对象w)可以迭代。当然,你可能发现Word类实现了序列协议(即__getitem__
和__len__
方法),而序列都可以迭代,不过去掉__len__
方法时Word类实例依然可以迭代。
2 迭代器
标准的迭代器接口有两个方法:
__next__
:返回下一个可用对象,如果没有元素了,抛出StopIteration
异常。__iter__
:返回self
,以便在应该使用可跌代对象的时候使用迭代器。
迭代器实现了无参的__next__
方法,返回序列的下一个元素;如果没有元素了,抛出StopIteration
异常。迭代器还实现了__iter__
(迭代器的__iter__
应该返回自身,即self
)方法,因此迭代器可以迭代。
示例2,实现一个迭代器类Reverse:迭代该对象实例将得到反转信息。
class Reverse:
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
>>> eng = ['one','two','three','four','fife']
>>> for i in Reverse(eng):
... print(i)
...
fife
four
three
two
one
>>>
3 生成器
所有生成器都是迭代器,因为生成器完全实现了迭代器的接口。
只要函数定义体中有yield
关键字,该函数就是生成器函数;调用生成器函数,会返回一个生成器对象:生成器会创建一个生成器对象,包装生成器函数的定义体;把生成器(gen)传给next(gen)
函数时,生成器函数会向前,执行函数定义体中下一个yield
语句,返回产出的值,并在函数定义体的当前位置暂停;最终,函数的定义体返回时,外层的生成器对象会抛出StopIteration
异常。
3.1 生成器函数
示例3,定义一个生成器函数gen_func
,该函数定义体只有3个yield
语句。
def gen_fun():
yield 1
yield 2
yield 3
调用生成器函数返回一个生成器对象,生成器是迭代器,会生产yield
语句的值。
>>> g = gen_func()
>>> g
<generator object gen_func at 0x0000016C0BAF5D58>
>>> for x in g:#
... print(x)
...
1
2
3
调用next()
返回下一个元素,即yield
生成的下一个元素;生成器函数执行完毕时,生成器对象会抛出StopIteration
异常。
>>> g = gen_func()
>>> next(g)#
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)#
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
3.2 yield from
语句
如果生成器函数需要产出另一个生成器的值,传统解决方法是使用嵌套的for循环。如下:
def chain1(*iterables):
for it in iterables:
for i in it:
yield i
上述代码与下述代码完全等效,yield from it
代替了内层的for
循环。
def chain2(*iterables):
for it in iterables:
yield from it
运行结果如下所示:
>>> s = 'ABC'
>>> t = (1,2,3)
>>> list(chain1(s,t))
['A', 'B', 'C', 1, 2, 3]
>>> list(chain2(s,t))
['A', 'B', 'C', 1, 2, 3]
4 iter()
内置函数
4.1 iter
函数作用:
-
检查对象是否实现了
__iter__
方法,如果实现了就调用它,获取一个迭代器(将可迭代对象转换为迭代器); -
如果没有实现
__iter__
方法,但实现了__getitem__
方法,Python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素; -
如果尝试失败,Python抛出
TypeError
异常,通常会显示C object is not iterable
(C对象不可迭代)。
注意:检查对象x
是否可迭代,最准确的方法是——调用iter(x)
函数,如果不可迭代,再处理TypeError
异常。这比使用isinstance(x, collections.abc.Iterable)
更准确,因为iter(x)
函数会考虑到遗留的__getitem__
方法,而abc.Iterable
则不考虑。
如下示例4定义了两个类:Word类实现了__getitem__
方法,Foo类实现了__iter__
方法。
class Word:
"""实现了__getitem__但没有实现__iter__"""
def __init__(self, text):
self.words = text.split(" ")
def __getitem__(self, index):
return self.words[index]
def __len__(self):
return len(self.words)
class Foo:
"""实现了__iter__"""
def __iter__(self):
pass
将可迭代对象传入iter
函数,会得到一个迭代器;调用next
函数会返回迭代器下一个元素,如果没有元素,next
函数会抛出StopIteration
异常。如下所示:
>>> w = Word("I am a student")
>>> it = iter(w)#iter(w)返回迭代器
>>> it
<iterator object at 0x0000016C0BAFD710>
>>> next(it)#调用next(it)返回迭代器下一个元素
'I'
>>> next(it)
'am'
>>> next(it)
'a'
>>> next(it)
'student'
>>> next(it)#如果没有元素,则抛出StopIteration异常
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
继续往下看,下面代码信息可看出:iter()
考虑遗留的__getitem__
方法,调用iter(w)
返回一个迭代器;而abc.Iterable
不考虑遗留的__getitem__
方法,它只能通过判断子类是否有__iter__
方法,有则是子类(如Foo类),否则就不是其子类(如Word类)。
>>> from collections import abc
>>> w = Word("I am a student")
>>> iter(w)
<iterator object at 0x0000016C0BB28AC8>
>>> isinstance(w, abc.Iterable)
False
>>> isinstance(Foo, abc.Iterable)
True
4.2 深入分析iter()
函数
iter
函数有一个鲜为人知的用法:传入两个参数,使用常规的函数或任何可调用对象创建迭代器。这样使用时:
- 第一个参数必须是可调用对象,用于不断调用(没有参数),产出各个值;
- 第二个参数是哨值,这是个标记值,当可调用对象返回这个值时,触发迭代器抛出
StopIteration
异常。
示例5代码,rdint6
函数随机返回1-6中的一个数字。将iter
函数第一个参数设为rdint6
,第二个参数(哨值)设为1
>>> from random import randint
>>> def rdint6():
... return randint(1,6)
...
>>> it = iter(rdint6,1)
>>> next(it)
2
>>> next(it)
2
>>> next(it)
6
>>> next(it)
5
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
示例6,iter
的一个例子——逐行读取文件,直到遇到空行。
with open('data.txt') as f:
for line in iter(f.readline, '\n'):
print(line)