python 迭代器与生成器
说到python迭代器,首先要明确两个概念:Iterable
和Iterator
,这两个概念还有Generator
都是定义在collections模块里的。
Iterable
意为“可迭代的(对象)”,包括如下两种:
1、实现了
__getitem__(self,index)
方法的对象这类对象用for关键字迭代时,索引从0开始递增,每次+1, 直到
__getitem__
方法抛出IndexError
或者StopIteration
错误则停止迭代。
2、实现了
__iter__(self)
方法的对象约定:返回的对象必须包含
__next__(self)
方法, 用for关键字迭代时,__next__
方法抛出StopIteration
错误则停止迭代。
一般来说,list、tuple都是实现了__getitem__(self,index)
方法因而可迭代,而dict则是实现了__iter__(self)
而可迭代。可迭代对象都可以被for关键字迭代,也可以用内建函数iter转化为迭代器对象。
Iterator
即迭代器对象。迭代器和可迭代对象不同。迭代器的要求是:
1、实现了
__next__(self)
方法 2、实现了__iter__(self)
方法约定:
__iter__(self)
方法应返回自身
以上的约定并非强制的,即使不遵守也可以通过isinstance的检查,但为避免错误,建议编写代码时遵守约定。
Generator
即生成器,生成器都实现了迭代器协议,因此所有的生成器都是迭代器。生成器除了迭代器的两个方法之外,还要包含如下方法:
send(self,value)
方法,用于向生成器发送值;
throw(self,error)
方法,用于向生成器抛入错误;close(self)
方法,用于关闭生成器;
如果一个迭代器具有上述三个方法,就被识别为生成器。
生成器的约定:
- send方法用于向生成器发送一个值,一个生成器接受的第一个值必须是None,即一个生成器第一次调用必须是
__next__
方法,该方法等价于send(None)
。
- close方法的效果是在生成器中产生一个
GeneratorExit
异常,异常的作用是触发生成器中可能的try...except...finally的finally块,或者with block的__exit__
方法。 - throw方法的作用是向生成器中抛入异常。可以抛入
GeneratorExit
异常。也可以抛入其他异常。生成器可以利用不同异常进行不同的善后处理,但不应捕获GeneratorExit
异常。
生成器当然可以使用class实现Generator
协议的各个方法和约定来实现。但通常,我们使用如下两个方法来创建生成器:
- 生成器解析式,类似列表解析,但将中括号替换成小括号。
- 使用yield来创建生成器。如果一个函数内部使用了yield,该函数执行的结果就是一个生成器。
关于后者,有如下知识点:
- 关键字yield的语法是
yield x
或者y=yield x
,其操作是先将x输出,然后调用send
方法或者__next__
方法后继续从yield后执行。
- 如果调用
send
方法,yield会接收传递的值并赋给y;如果调用__next__
方法,yield接收到的值为None。 - 调用
throw
方法可以将异常抛入生成器内,异常从yield处进入生成器,随后就进入异常处理途径,被捕获处理或者继续向外抛出。GeneratorExit
异常不应被处理。 - 调用
close
方法生成的GeneratorExit
异常也是从yield处出现的。close方法会在最后捕获该异常,不会抛出该异常到外部。 - 生成器代码块里的return语句的返回值将作为StopIteration异常类的构造函数的参数来生成该异常,捕获该异常,其value字段即为返回值。
附注:协程旧语法yield from的知识点
- 关键字
yield from
的语法为x=yield from B
,其中B为一个生成器。生成的协程函数(设其为A)同样满足生成器协议。
- 所有通过
send
方法发送到A的数据,如果值为None则调用B的__next__
方法;否则将调用B的send
方法将值直接传递给B。 - 如果调用A的
close
方法,则B的close
方法也会被调用。 - 如果通过A的
throw
方法抛入GeneratorExit
异常,会调用B的close
方法,然后再从A的yield from
处抛出该异常。 - 如果通过A的
throw
方法抛入非GeneratorExit
的异常,会调用B的throw
方法将异常直接抛入B中。 - 如果B抛出
StopIteration
异常,该异常的value字段值将赋给x,而A将从yield from
后继续执行。 - 如果B抛出非
StopIteration
的异常,则该异常会从yield from
处抛入A中。
yield from
目前基本不被用作协程之间的交流了,现在都用async...await
语法来实现协程。但是yield from
仍然有用,也就是可以用作生成器的修饰,即增加生成器的上下文处理。