Python生成器和协程

生成器

生成器对象是迭代器对象,更是可迭代对象,它实现了__iter____next__协议

Python通过在函数中使用yield关键字来定义一个生成器工厂函数,当工厂函数调用时生成一个生成器对象

def gen(a):
    print('Started...')
    yield a
    yield a + 10
    print('End...')

yield关键字有两个作用:

  • 产出, 当使用next(Generator)内置函数它会产出yield expr语句中expr的值
  • 让步, 程序执行完yield右侧的表达式后会暂停执行并将控制权交给调用方

当生成器运行到定义体的结尾会抛出StopIteration异常。如果函数体内部有return expr语句,当执行此语句时会抛出StopIteration异常并结束此生成器,同时return expr语句中的expr会被赋值给StopIteration.value

# 这是执行return语句时终端打印的信息,expr即是return的返回值

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration: expr

for循环语句中迭代生成器对象,不会抛出异常是因为Python语言内部会处理for循环和其他迭代上下文(如列表推导、元组拆包等等)中的StopIteration异常

协程

协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值。

通过在生成器函数内部使用Received = yield expr表达式让普通的生成器进化为协程。调用方通过生成器的send方法发送消息,并返回yield产出的值

def coro(a):
    print('-> Started: a =', a)
    b = yield a
    print('-> Received: b =', b)
    c = yield a + b
    print('-> Received: c =', c)

if __name__ == '__main__':
    coroObject = coro(14)
    next(coroObject)
    coroObject.send(28)
    coroObject.send(99)

使用协程,第一步必须使用next内置函数预激协程。

send(value)方法将value值赋给Received = yield expr表达式中的Received而不是expr,该方法的返回值是expr的值。协程之所以要预激,是因为send方法赋值需要程序先运行到=,进行赋值

yield from语法

def subGen():
    for i in range(5):
        rece = yield i
        if rece is None:
            break
    return 'end'

def gen():
    while True:
        result = yield from subGen()
        print(result)

yield from的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码.

调用方发送的值传递给子生成器并阻塞委派生成器,子生成器只有执行return语句才会结束阻塞并将return语句的返回值赋给received

yield from语法的意义是:

  • 协程需要预激,yield from会预激子生成器
  • 处理子生成器运行结束时抛出的StopIteration异常
  • 处理子生成器执行return语句抛出的StopIteration异常,并把return语句的返回值绑定给yield from表达式左侧变量
  • 建立双向通道,调用方调用委派生成器的send方法时,发送值传递给子生成器Received = yield exprReceived变量中;同时将子生成器yield <expr>中的expr作为委派生成器send方法的返回值

委派生成器本身也是生成器,当委派生成器结束时也会抛出StopIteration异常。所以一般在委派生成器中使用循环,避免抛出异常

原文地址:https://www.cnblogs.com/weixia-blog/p/12945322.html