Python数据类型(四)迭代器 & yield表达式

迭代器类型是指Python中通过使用特定的单独方法支持在容器中进行迭代的类型。容器对象要提供迭代支持,必须定义container.iter()方法来返回一个迭代器对象,并且迭代器对象本身需要支持iterator.__iter__()iterator.__next__(),这两者共同组成了迭代器协议。Python 定义了几种迭代器对象以支持对一般和特定序列类型、字典和其他更特别的形式进行迭代。 除了迭代器协议的实现,特定类型的其他性质对迭代操作来说都不重要。

container.iter()

​ 返回一个迭代器对象,该对象需要支持迭代协议。如果容器支持不同的迭代类型,则可以提供额外的方法来专门地请求不同迭代类型的迭代器。 (支持多种迭代形式的对象的例子有同时支持广度优先和深度优先遍历的树结构。) 此方法对应于 Python/C API 中 Python 对象类型结构体的 tp_iter 槽位。

迭代器协议

​ 迭代器协议由iterator.__iter__()iterator.__next__()构成。

​ iterator.__iter______()

​ 返回迭代器对象本身,实现该方法是同时允许容器和迭代器配合forin语句使用的必要条件,此方法对 应于 Python/C API 中 Python 对象类型结构体的 tp_iter槽位。

​ iterator.__next______()

​ 从迭代器中返回下一项,如果已经没有项可返回,则会引发StopInteration异常,此方法对应于 Python/C API 中 Python 对象类型结构体的 tp_iternext槽位。

迭代器协议实现原理

大多数容器都能使用for语句,如下:

>>> for element in [1,2,3]:
    print (element)

1
2
3
>>> for element in (1,2,3):
    print (element)

1
2
3
>>> for element in {'one':1, 'two':2}:
    print (element)

one
two
>>> for char in '123':
    print (char)

1
2
3
>>> 

以上访问方式清晰、简洁、方便,迭代器的使用渗透并统一了Python,for语句在底层对容器对象调用iter(),函数返回一个迭代器对象,该对象定义方法__next______(),该方法一次访问一个容器中的元素。当没有更多的元素时,__next______()会引发一个stopIteration异常,通知for循环终止。可以使用next()内置函数调用__next______()方法,举例如下:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<str_iterator object at 0x102156828>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
  File "<pyshell#147>", line 1, in <module>
    next(it)
StopIteration

以上就是迭代器协议的实现原理,知道迭代器协议实现原理之后,可以给自己的类添加迭代器。首先定义一个__iter______()返回一个带有__next______()方法的对象,如果已经定义了__next______(),则___iter______()返回self。

>>> 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]

自定义好上面的迭代器对象之后,调用如下:

>>> rev = Reverse('spam')
>>> iter (rev)
<__main__.Reverse object at 0x103b81f98>
>>> for char in rev:
    print (char)


m
a
p
s
>>> 

以上是迭代器协议的原理和实践应用。此外,Python的generator提供了一种实现迭代器协议更便捷的方式。

generator ——生成器

​ generator是一个用于创建迭代器的简单而强大的工具,写法类似标准函数,但当要返回数据时会使用 yield表达式,每次对生成器调用next()时,会从上次离开位置执行(它会记住上次执行语句时的所有数据值)。

generator的创建示例和使用示例如下:


>>> #generator创建示例
>>> def reverse (data):
    for index in range (len(data)-1, -1, -1):
        yield data[index]

>>> #generator使用示例
>>> for char in reverse('golf'):
    print (char)


f
l
o
g

以用生成器来完成的操作同样可以用前面所描述的基于类的迭代器来完成。 但生成器的写法更为紧凑,因为它会自动创建__iter______()和 __next______()方法。另一个关键特性在于局部变量和执行状态会在每次调用之间自动保存。 这使得该函数相比使用 self.indexself.data 这种实例变量的方式更易编写且更为清晰。除了会自动创建方法和保存程序状态,当生成器终结时,它们还会自动引发StopInteration。这些特性结合在一起,使得创建迭代器能与编写常规函数一样容易。

生成器表达式

​ 生成器表达式是用圆括号括起来的紧凑形式生成器标注,圆括号在只附带一个参数的调用中可以被省略。

一些简单的生成器可以写成简洁的表达式代码,所用语法类似列表推导式,将外层为圆括号而非方括号。 这种表达式被设计用于生成器将立即被外层函数所使用的情况。 生成器表达式相比完整的生成器更紧凑但较不灵活,相比等效的列表推导式则更为节省内存。

生成器表达式示例:

>>> generator_expression ::= "('expression comp_for')"

​ 当为生成器对象调用__next______()方法时(与普通生成器的方式相同),将延迟计算生成器表达式中使用的变量。但是,会立即对最左边的for子句进行计算,以便在处理生成器表达式的代码中的任何其他可能的错误之前可以看到它所产生的错误。不能立即计算后面的for子句的原因是使用的变量可能依赖前面的for循环。例如表达式:(x*y for x in range(10) for y in bar(x))。

生成器表达式代码示例如下:

>>> sum (i*i for i in range(10))
285
>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum (x*y for x,y in zip(xvec, yvec))
260
>>> from math import pi, sin
>>> sine_table = {x:sin(x*pi/180) for x in range(0, 91)}
>>> data = 'golf'
>>> list (data[i] for i  in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']
>>> 

生成器-迭代器方法

​ 生成器迭代器的方法可被用于控制生成器函数的执行,但若是在生成器已经在执行时调用生成器-迭代器方法会引发ValueError。

生成器—迭代器方法如下:

​ generator.___next___():

​ 开始一个生成器函数的执行或是从上次执行的 yield 表达式位置恢复执行。当一个生成器函数通过___next_______()方法恢复执行时,当前的 yield 表达式总是取值为None。随后会继续执行到下一个 yield 表达式,其expression_list的值会返回给___next_______()的调用者。如果生成器没有产生下一个值就退出,则会引发StopIteration异常。调用该方法通常是因是调用系统函数next()来隐式调用。

​ generator.send(value)

​ 恢复执行并向生成器函数“发送”一个值。 value 参数将成为当前 yield 表达式的结果。send()方法会返回生成器所产生的下一个值,或者如果生成器没有产生下一个值就退出则会引发StopInteration。当调用send()方法来启动生成器时,必须以None作为调用参数,因为没有可以接收值的 yield 表达式。

​ generator.throw(type, value, traceback)

​ 在生成器暂停的位置引发type类型的异常,并返回该生成器函数所产生的下一个值。 如果生成器没有产生下一个值就退出,则将引发StopIteration异常。如果生成器函数没有捕获传入的异常,或引发了另一个异常,则该异常会被传播给调用者。

​ generator.close()

​ 在生成器暂停的位置引发GenerationExit。 如果之后生成器函数正常退出、关闭或引发 GenerationExit (由于未捕获该异常) 则关闭并返回其调用者。如果生成器产生了一个值,关闭会引发RuntimeError。如果生成器引发任何其他异常,它会被传播给调用者。 如果生成器已经由于异常或正常退出则 close()不会做任何事情。

生成器方法示例如下:

>>> def echo(value=None):
    print ("Execution starts when 'next()' is called for the first time.")
    try:
        while True:
            try:
                value = (yield value)
            except Exception as e:
                value = e
    finally:
        print ("Don't forget to clean up when 'close()' is called.")


>>> generator = echo(1)
>>> print (next(generator))
Execution starts when 'next()' is called for the first time.
1
>>> print (next(generator))
None
>>> print (generator.send(2))
2
>>> generator.throw(TypeError, 'spam')
TypeError('spam',)
>>> generator.close()
Don't forget to clean up when 'close()' is called.
>>> 

yield表达式

​ yield表达式在定义generator函数或是asynchronous generator的时候才会用到,因此只能在函数定义的内部使用yield表达式。在一个函数体内使用yield表达式会使这个函数变成一个生成器,并且在一个asyn def 函数体内使用yield表达式会让协程函数变成异步的生成器。

def gen():
  yield 123

async def agen():
  yield 123

yield表达式在生成器中的调用步骤

1.当前面讲的生成器函数被调用的时候返回一个生成器,该生成器控制生成器函数的执行。

2.生成器某个方法被调用的时候,生成器函数开始执行,此时会一直执行到第一个yield表达式

3.执行到第一yield表达式之后,执行此时中断再次被挂起,给生成器的调用者返回expression_list的值。

4.挂起后,所有局部状态都被保留下来,包括局部变量的当前绑定,指令指针,内部求值栈和任何异常处理的状态。

5.通过调用生成器的某一个方法,生成器函数继续执行,此时函数的运行就和 yield 表达式只是一个外部函数调用的情况完全一致。

6.恢复后 yield 表达式的值取决于调用的哪个方法来恢复执行,如果用的是___next_______()方法(通常通过语言内置的for或是next()调用),那么调用结果就是None。否则,如果用send(),那么结果就是传递给send()方法的值。

yield表达式的其它特点

1.所有的生成器函数与协程函数相似,都是被yield多次,具有多个入口,并且执行可以被挂起。区别是生成器函数不能控制在yield后交给哪里继续执行,控制权总是转移到生成器的调用者。

2.在try结构中任何位置都允许yield表达式,如果生成器在(因为引用计数到零或是因为被垃圾回收)销毁之前没有恢复执行,将调用生成器-迭代器的close()方法,close()方法允许任何挂起的finally字句执行。

3.当使用 yield from<expr>表达子句时,会将所提供的表达式视为一个子迭代器。这个子迭代器产生的所有值都直接被传递给当前生成器方法的调用者。通过send()传入的任何值以及通过throw()传入的任何异常如果有适当的方法则会被传给下层迭代器。 如果不是这种情况,那么send()将引发AttributeErrror或TypeError,而throw()将立即引发所传入的异常。

4.当yield表达式是赋值语句右侧的唯一表达式时,括号可以省略。

异步生成器

自从Python 3.6后,如果生成器出现在async def函数中,那么允许async for子句和await表达式,就像异步理解一样。如果生成器表达式包含async for子句或await表达式,则称为异步生成器表达式Asynchronous Generator表达式生成一个新的Asynchronous Generator对象,即一个异步生成器

​ 当一个异步生成器函数被调用时,它会返回一个名为异步生成器对象的异步迭代器。 此对象将在之后控制该生成器函数的执行。 异步生成器对象通常被用在协程函数的 async for语句中,类似于在for语句中使用生成器对象。

异步生成器执行步骤

​ 1.异步生成器调用方法之一是返回awaitable对象,执行会在此对象被等待时启动。

​ 2.启动时,代码执行到第一个yield表达式。

​ 3.执行到yield表达式之后,会暂停,进行挂起,将expression_list的值返回给等待中的协程。

​ 4.挂起时,所有的状态会被保留,包括局部变量的当前绑定、指令的指针、内部求值的堆栈以及任何异常处理的状态。

​ 5.通过调用异步生成器的某一方法,异步生成器继续被执行,此时函数的运行就和 yield 表达式只是一个外部函数调用的情况完全一致。

​ 6.恢复后 yield 表达式的值取决于调用的哪个方法来恢复执行,如果用的是___next_______()方法(通常通过语言内置的asyn for或是next()调用),那么调用结果就是None。否则,如果用asend(),那么结果就是传递给asend()方法的值。

异步生成器其它特性

​ 1.在异步生成器函数中,try构造中的任何地方都允许使用yield表达式。但是,如果异步生成器在完成之前未恢复(通过达到零引用计数或被垃圾收集),则try构造中的yield表达式可能导致无法执行挂起的finally子句。在这种情况下,运行异步生成器的事件循环或调度程序负责调用异步生成器迭代器的aclose()方法并运行生成的coroutine对象,从而允许执行任何挂起的finally子句。

​ 2.为了处理最后循环终结,循环事件应定义一个终结函数,该函数使用异步生成器迭代器,能调用aclose()并执行协程。可以通过调用sys.set_asyncgen_hooks()注册终结函数,当第一次迭代时,异步生成器迭代器将存储注册的终结器,以便在终结时调用。

​ 3.yield from <expr>表达式如果在异步生成器函数中使用会引发语法错误。

异步生成器-迭代器方法

​ 异步生成器迭代器方法可以用来控制异步生成器函数的执行。

​ coroutine agen.___anext_______()

​ 返回一个awaitable对象,当运行开始执行异步生成器或在最后一个执行的yield表达式处恢复该awaitable对象。当使用___anext_______()方法恢复异步生成器函数时,当前yield表达式在返回的awaitable中始终计算为none,当运行时,将继续计算下一个yield表达式。expression_list中的值是完成协同程序引发的stopIteration异常的值。如果异步生成器退出而不生成另一个值,那么awaitiable将引发StopAsyncIteration异常,指示异步迭代已完成。该方法通常是通过async for 循环隐式地调用。

​ coroutine agen.asend(value)

​ 返回一个可等待对象,它在运行时会恢复该异步生成器的执行。 与生成器的 send() 方法一样,此方法会发送一个值给异步生成器函数,其 value 参数会成为当前 yield 表达式的结果值。 asend() 方法所返回的可等待对象将返回生成器产生的下一个值,其值为所引发的 StopIteration,或者如果异步生成器没有产生下一个值就退出则引发 StopAsyncIteration。 当调用 asend() 来启动异步生成器时,它必须以 None 作为调用参数,因为这时没有可以接收值的 yield 表达式。

​ coroutine agen.athrow(type, value, traceback)

​ 返回在异步生成器暂停时引发类型为的异常的可等待项,并返回生成器函数生成的下一个值作为引发的StopIteration异常的值。如果异步生成器退出而不生成另一个值,则等待程序将引发StopAsyncIteration异常。如果生成器函数没有捕获传入的异常,或者引发了不同的异常,那么当运行awaitable时,该异常会传播到awaitable的调用方。

​ coroutine agen.aclose()

​ 返回一个awaitable对象,该对象会在运行时向异步生成器函数暂停的位置抛入一个 GeneratorExit。 如果该异步生成器函数正常退出、关闭或引发 GeneratorExit(由于未捕获该异常) 则返回的可等待对象将引发 StopIteration异常。 后续调用异步生成器所返回的任何其他可等待对象将引发 StopAsyncIteration异常。 如果异步生成器产生了一个值,该可等待对象会引发 RuntimeError。 如果异步生成器引发任何其他异常,它会被传播给可等待对象的调用者。 如果异步生成器已经由于异常或正常退出则后续调用 aclose()将返回一个不会做任何事的可等待对象。