迭代器和生成器
摘要
重点是两个协议,协议一__iter__
和协议二__next__
。PEP 234
- 可迭代对象(Iterable)
定义:实现了
__iter__
方法的对象 用途:iter函数调用__iter__
方法,将对象转换为迭代器 备注:可迭代对象的意思是指其可以变成一个迭代器,且每次重新计数; - 迭代器(Iterator)
定义:实现了
__iter__
方法和__next__
方法 用途:取数据 备注:迭代器也实现协议一,是为了让迭代器表现的像序列(在for循环中),其返回自身,即意味着不会重新计数。 - 生成器(generator) 定义:列表生成器和使用yield定义生成器函数 用途:产生数据
Iterable
只要实现了__iter__
方法的对象,即为Iterable。
Python常见可迭代对象:
- 集合或序列类型(list、tuple、set、dict、str)
- 文件对象
注意:
- 虽然实现了
__iter__
方法即为Iterable对象,但是若想在for循环中正常使用,就要保证__iter__
方法正确返还Iterator对象。 - 即使不实现
__iter__
方法即不是可迭代对象,也可以在for循环中正常使用,需要对象实现__getitem__
方法。
原因是,for
循环中使用了iter
函数来转换,iter函数优先调用对象的__iter__
方法,否则调用__getitem__
方法,两种方法均可以转换为迭代器对象,以下是iter()
函数的Python等价实现:
def my_iter(iterable):
if hasattr(iterable, '__iter__'):
return iterable.__iter__()
elif hasattr(iterable, '__getitem__'):
index = 0
while True:
try:
value = iterable.__getitem__(index)
index += 1
yield value
except IndexError:
break
else:
raise TypeError('Not iterable')
正确实现了__iter__
方法的可迭代对象:
class IterObj:
def __init__(self):
self.a = [3, 5, 7, 11, 13, 17, 19]
def __iter__(self):
return iter(self.a)
实现了__getitem__
方法的对象亦可以通过iter函数转换为生成器:
class IterObj:
def __init__(self):
self.a = [3, 5, 7, 11, 13, 17, 19]
def __getitem__(self, i):
return self.a[i]
it = IterObj()
print(isinstance(it, Iterable)) # false
print(isinstance(it, Iterator)) # false
print(isinstance(it, Generator)) false
print(hasattr(it, "__iter__")) # false
print(iter(it)) # <iterator object at 0x10b231278>
for i in it:
print(i) # 将打印出3、5、7、11、13、17、19
Iterator
迭代器一定是可迭代对象,且额外实现了__next__
方法
定义:
- 实现
__next__
方法,要么返回下一个值,要么返回StopIteration
或其子类; - 实现
__iter__
方法,返回self
。
class IterObj:
def __init__(self):
self.a = [3, 5, 7, 11, 13, 17, 19]
self.n = len(self.a)
self.i = 0
def __iter__(self):
self.i = 0
return self
def __next__(self):
while self.i < self.n:
v = self.a[self.i]
self.i += 1
return v
else:
self.i = 0
raise StopIteration()
Python中很多接口是通过调用next()
函数来交互的,如for
循环、map()
等,所以对于Iterable
对象,要通过内置的iter
方法,将其转换为Iterator
。
Iterable
和Iterator
在用法上的区别在于,Iterable
可以通过iter()
生成多个/多次不同的Iterator
,每次重头计数。而一个Iterator
即使被iter()
调用,返回的仍是自己,意味着计数不清零。
Generator
生成器既是可迭代对象,又是迭代器。 生成器有两种定义方式:
- 列表生成器
- 使用yield定义生成器函数
列表生成器:
g = (x * 2 for x in range(10)) # 0~18的偶数生成器
print(isinstance(g, Iterable)) # true
print(isinstance(g, Iterator)) # true
print(isinstance(g, Generator)) # true
print(hasattr(g, "__iter__")) # true
print(hasattr(g, "__next__")) # true
print(next(g)) # 0
print(next(g)) # 2
生成器函数:
def gen():
for i in range(10):
yield i
生成器的嵌套
假设有三个生成器A、B和C,试图通过A取B的数据,B取C的数据,C取最原始的数据,即实现三个生成器的嵌套,该如何操作?
可以通过yield from
实现生成器的嵌套。
def generatorC():
for i in range(5):
yield i
def generatorB():
yield from generatorC()
def generatorA():
yield from generatorB()
# 使用生成器A来获取生成器C的输出
for num in generatorA():
print(num)
通过yield from
实现嵌套的两个生成器,分别是委托生成器和子生成器。其中委托生成器,只起一个桥梁作用,它建立的是一个双向通道,它并没有权利也没有办法,对子生成器yield
回来的内容做拦截,而是将子生成器yield
的数据传递给调用方。
如果希望委托生成器可以修改子生成器,即A可以修改B的数据,B可以修改C的数据,该如何操作?
此时不再适用yield from
,而是通过yield
。
def generatorC():
for i in range(5):
yield i
def generatorB():
for num in generatorC():
num = do_something(num)
yield num
def generatorA():
for num in generatorB():
num = do_something(num)
yield num
# 使用生成器A来获取最终的输出
for num in generatorA():
print(num)