首先看一个例子:Python中list可迭代(Iterable),但是并不是迭代器(Iterator)。
from collections.abc import Iterable, Iterator
a = [1, 2, 3]
print(isinstance(a, Iterator)) # 结果为 False
print(isinstance(a, Iterable)) # 结果为 True
1.可迭代对象 Iterable
根据Python的迭代协议,只要类内部包含了iter这个魔法函数,那么这个类的对象就是可迭代对象。
from collections.abc import Iterable, Iterator
class A:
def __iter__(self):
pass
print(isinstance(A(), Iterable)) # 结果为 Frue
print(isinstance(A(), Iterator)) # 结果为 False
只要是可迭代对象,就可以使用for对其进行遍历。
for x in A():
print(x)
但是类A中仅定义了iter函数,并没有具体实现,所以使用for对其进行遍历的时候出现了以下错误。
TypeError: iter() returned non-iterator of type 'NoneType'
很明显,iter魔法函数需要返回一个迭代器,所以我们对类A稍作修改。
class A:
def __iter__(self):
return iter([1, 2, 3, 4])
再使用for进行遍历的时候,以此打印出1、2、3、4。
2.迭代器 Iterator
一个类对象如果要成为迭代器,其类中必须同时包含iter和next两个魔法函数。
from collections.abc import Iterable, Iterator
class B:
def __init__(self):
self.index = 0
self.list = [1, 2, 3, 4, 5]
def __iter__(self):
return self
def __next__(self):
if self.index == len(self.list):
raise StopIteration
value = self.list[self.index]
self.index += 1
return value
print(isinstance(B(), Iterable)) # 结果为 True
print(isinstance(B(), Iterator)) # 结果为 True
但直接使用迭代器迭代的时候,如果完成了一轮遍历,就无法迭代了。
所以,通常在iter函数中返回一个迭代器。
from collections.abc import Iterable, Iterator
class B:
def __iter__(self):
return MyMyIterator()
class MyIterator(Iterator):
def __init__(self):
self.index = 0
self.list = [1, 2, 3, 4, 5]
def __next__(self):
if self.index == len(self.list):
raise StopIteration
value = self.list[self.index]
self.index += 1
return value
if __name__ == '__main__':
b = B()
print(isinstance(b, Iterable))
print(isinstance(b, Iterator))
for x in b:
print(x)
# 因为__iter__()返回迭代器,所以可以反复遍历
for x in b:
print(x)
3.生成器 generator
3.1 使用生成器表达式创建生成器
print(type((x for x in range(1, 10)))) # 结果 <class 'generator'>
print(type((x for x in B()))) # 结果 <class 'generator'>
3.2 使用yeild创建生成器
def fib(index):
n, a, b = 0, 0, 1
while n < index:
yield b
a, b = b, a + b
n += 1
if __name__ == '__main__':
gen = fib(10)
print(gen)
print(type(gen)) # 结果 <class 'generator'>
print(isinstance(gen, Iterable)) # True
print(isinstance(gen, Iterator)) # True
从最后的结果可以看到,generator也是Iterator,当然也是Interable(Iterator继承自Iterable)。
因此,可以将generator作为iter中的函数返回值,来构造一个Iterable。
将前面的Iterable类A进行改造如下:
class A:
def __iter__(self):
return (x for x in range(1, 5))
或者使用生成器函数
from collections.abc import Iterable, Iterator
def fib(index):
n, a, b = 0, 0, 1
while n < index:
yield b
a, b = b, a + b
n += 1
class B:
def __iter__(self):
return fib(10)
b = B()
print(isinstance(b, Iterable))
print(isinstance(b, Iterator))
for x in b:
print(x)
3.3 send、close、throw
生成器类中有个3个函数send、close、throw。
send: 生成器下一次迭代时,调用者向生成器中传递一个值。
close: 关闭生成器,如果继续迭代则会出现StopIteration异常。
trow: 主动向生成器抛出异常。
4.yield
上文提到了,包含yield的函数就是一个生成器,这实际上跟yield的特性有关。
yield可以让程序在一次迭代后暂停,下次迭代开始时程序还能在暂停的位置恢复并继续运行,而这刚好和生成器的特征吻合。
另外,上文提到了生成器的send函数可以使调用者向生成器传递值,而yield刚好能够接收到这个值。
def gen1():
x = yield 1
print('a', x)
yield test(x)
print('b', x)
x = yield 3
print('c', x)
x = yield 4
print('d', x)
if __name__ == '__main__':
g1 = gen1()
print(g1.send(None))
print(g1.send(222))
print(g1.send(333))
print(g1.send(444))
上面的代码中,生成器迭代一次后,程序停在了yield 1
位置;
然后第二次迭代时,通过send函数传入了值222,此时程序又从yield 1
位置恢复,从方便理解的角度可以认为传入的值到了x = yield 1
中的yield 1
,也就是代码的等号右侧,根据代码逻辑,等号右侧的值赋值给了左侧的x
。
需要注意的时,生成器第一次迭代的时候,是无法传入值的,只能send(None)或者使用next()进行首次迭代,因为这个时候程序刚刚运行到yield这里,而不是从yield位置恢复。
5.yield form
yield from
能够在暂停、恢复程序的同时,将Iterable对象转成generator。
def gen2():
yield from [1, 2, 3, 4, 5]
if __name__ == '__main__':
g2 = gen2()
print(type(g2))
for x in g2:
print(x)
网友评论