Python 协程详解

本文详细介绍了Python中的协程,从生成器如何进化为协程,到协程的基本行为,包括四个状态:创建、运行、暂停和关闭。重点讨论了协程的预激、异常处理和获取返回值的方法。通过示例展示了如何使用协程计算平均值,并介绍了yield from的意义和使用。此外,还阐述了协程在离散事件仿真中的应用,强调了协程在实现并发和事件驱动编程中的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

yield item这行代码会产出一个值,提供给next()的调用方;此外还会做出让步,暂停执行生成器,让调用方继续工作,知道需要使用另一个值再调用next()。调用方会从生成器中拉取值。

从语法上来看,协程与生成器类似,都是从定义体中包含yield关键字的函数。可是,在协程中,yield通常出现在表达式的右边(如data = yield),可以产出值,也可以不产出:如果yield关键字后面没有表达式,那么生成器产出None。协程可能会从调用方接收数据,不过调用方把提供数据给协程使用的方式是.send(data)方法。调用方会把值推送给协程。

yield关键字甚至可以不接受或传出数据。不管数据如何流动,yield都是一种流程控制工具,使用它可以实现协作式多任务;协程可以把控制器让步给中心调度程序,从而激活其他的协程。

综上,如果从根本上把yield视为控制流程的方式,这样就好理解协程了

协程可以认为是可以明确标记有某种语法元素(yield from)的阶段“暂停”函数

生成器如何进化为协程

协程的框架是在Python2.5(2006年)实现的。自此之后,yield关键字可以在表达式中使用,并且生成器增加了.send(value)方法。生成器的调用方法可以使用.send()发送数据,发送的数据会成为生成器中yield表达式的值。因此生成器可以作为协程使用。协程是指一个过程,这个过程与调用方协作,产出调用方法提供的值。

除了.send()方法,后续还增加了.throw()和.close()方法:前者作用是让调用方抛出异常,在生成器中处理;后者作用是终止生成器。

用作协程的生成器的基本行为

示例,一个简单的协程

def simple_coroutine():
    print('-> coroutine started')
    x = yield
    print('-> coroutine received:', x)


my_coro = simple_coroutine()
print(my_coro)  # 得到的是生成器对象
print(next(my_coro))  # None
print(my_coro.send(42))
打印
<generator object simple_coroutine at 0x00D957B0>
-> coroutine started
None
-> coroutine received: 42
Traceback (most recent call last):
  File "C:/Users/lijiachang/PycharmProjects/collect_demo/test2.py", line 13, in <module>
    print(my_coro.send(42))
StopIteration

知识点:

  1. 定义体中x = yield 这种形式,如果协程只需要从客户那接收数据,那么产出值是None,所以next(my_coro)的值为None,这个值是隐式指定的,因为yield关键字右边没有表达式。

  1. 首先要调用next(my_coro),因为生成器还没有启动,没在yield语句处暂停,所以一开始是无法发送数据的,首先要调用next(),到yield关键字处暂停。

  1. 当执行到my_coro.send(42),协程定义体中的yield表达式会得到42,然后赋值给x;协程继续运行到下一个yield表达式,或者终止。

  1. 最后,控制权流动到协程定义体的末尾,导致生成器像往常一样抛出StopIteration异常

协程的四个状态

协程可以身处四个状态中的一个。获取当前状态可以使用inspect.getgeneratorstate()函数确定,该函数会返回下面四种状态的一个:

  • 'GEN_CREATED' :等待开始执行

  • 'GEN_RUNNING' :解释器正在执行 (只有在多线程程序才能看到这个状态)

  • 'GEN_SUSPENDED' :在yield表达式处暂停

  • 'GEN_CLOSED' : 执行结束

因为send方法的参数会成为暂停的yield表达式的值,所以仅当协程处于暂停状态时才能调用send方法,例如my_coro.send(42).。

不过,如果协程还没激活(即状态为GEN_CREATED),始终要调用next(my_coro)激活协程----也可以调用my_coro.send(None)激活。

ps:如果创建协程对象后立即把None之外的值发给它,会出现以下错误,清晰明了:TypeError: can't send non-None value to a just-started generator

最先调用next(my_coro)函数这一步通常称为“预激”(prime)协程,即让协程向前执行到第一个yield表达式,准备好作为活跃的协程使用。

from inspect import getgeneratorstate

def simple_coro2(a):
    print('-> started: a=', a)
    b = yield a
    print('->  received: b=', b)
    c = yield a + b
    print('->  received: c=', c)


my_coro2 = simple_coro2(14)
print(getgeneratorstate(my_coro2))

print(next(my_coro2))
print(getgeneratorstate(my_coro2))

print(my_coro2.send(28))

print(my_coro2.send(99))
打印
GEN_CREATED
-> started: a= 14
14
GEN_SUSPENDED
->  received: b= 28
42
->  received: c= 99
Traceback (most recent call last):
  File "C:/Users/lijiachang/PycharmProjects/collect_demo/test2.py", line 19, in <module>
    print(my_coro2.send(99))
StopIteration

知识点:

  1. 协程在yield关键字所在的位置暂停执行。

  1. 在赋值语句中,=右边的代码在赋值前执行。因此对于b = yield a这行代码,等到客户端代码再激活协程时才会设定b的值。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值