python协程:yield的使用

本文和大家分享的主要是python协程yield相关内容,一起来看看吧,希望对大家学习python有所帮助。

协程定义

协程的底层架构是在pep342 中定义,并在python2.5 实现的。

python2.5 中,yield关键字可以在表达式中使用,而且生成器API中增加了 .send(value)方法。生成器可以使用.send(...)方法发送数据,发送的数据会成为生成器函数中yield表达式的值。

协程是指一个过程,这个过程与调用方协作,产出有调用方提供的值。因此,生成器可以作为协程使用。

除了 .send(...)方法,pep342 和添加了 .throw(...)(让调用方抛出异常,在生成器中处理)和.close()(终止生成器)方法。

python3.3后,pep380对生成器函数做了两处改动:

· 生成器可以返回一个值;以前,如果生成器中给return语句提供值,会抛出SyntaxError异常。

· 引入yield from 语法,使用它可以把复杂的生成器重构成小型的嵌套生成器,省去之前把生成器的工作委托给子生成器所需的大量模板代码。

协程生成器的基本行为

首先说明一下,协程有四个状态,可以使用inspect.getgeneratorstate(...)函数确定:

· GEN_CREATED # 等待开始执行

· GEN_RUNNING # 解释器正在执行(只有在多线程应用中才能看到这个状态)

· GEN_SUSPENDED # 在yield表达式处暂停

· GEN_CLOSED # 执行结束

#! -*- coding: utf-8 -*-import inspect

# 协程使用生成器函数定义:定义体中有yield关键字。def simple_coroutine():

print(’-> coroutine started’)

# yield 在表达式中使用;如果协程只需要从客户那里接收数据,yield关键字右边不需要加表达式(yield默认返回None)

x = yield

print(’-> coroutine received:’, x)

my_coro = simple_coroutine()

my_coro # 和创建生成器的方式一样,调用函数得到生成器对象。# 协程处于 GEN_CREATED (等待开始状态)

print(inspect.getgeneratorstate(my_coro))

my_coro.send(None)# 首先要调用next()函数,因为生成器还没有启动,没有在yield语句处暂停,所以开始无法发送数据# 发送 None 可以达到相同的效果 my_coro.send(None)

next(my_coro)# 此时协程处于 GEN_SUSPENDED (在yield表达式处暂停)

print(inspect.getgeneratorstate(my_coro))

# 调用这个方法后,协程定义体中的yield表达式会计算出42;现在协程会恢复,一直运行到下一个yield表达式,或者终止。

my_coro.send(42)

print(inspect.getgeneratorstate(my_coro))

运行上述代码,输出结果如下

GEN_CREATED

-> coroutine startedGEN_SUSPENDED

-> coroutine received: 42

# 这里,控制权流动到协程定义体的尾部,导致生成器像往常一样抛出StopIteration异常Traceback (most recent call last):

File "/Users/gs/coroutine.py", line 18, in <module>

my_coro.send(42)

StopIteration

send方法的参数会成为暂停yield表达式的值,所以,仅当协程处于暂停状态是才能调用send方法。

如果协程还未激活(GEN_CREATED 状态)要调用next(my_coro) 激活协程,也可以调用my_coro.send(None)

如果创建协程对象后立即把None之外的值发给它,会出现下述错误:

>>> my_coro = simple_coroutine()>>> my_coro.send(123)

Traceback (most recent call last):

File "/Users/gs/coroutine.py", line 14, in <module>

my_coro.send(123)TypeError: can’t send non-None value to a just-started generator

仔细看错误消息

can’t send non-None value to a just-started generator

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

再看一个两个值得协程

def simple_coro2(a):

print(’-> coroutine started: a =’, a)

b = yield a

print(’-> Received: b =’, b)

c = yield a + b

print(’-> Received: c =’, c)

my_coro2 = simple_coro2(14)

print(inspect.getgeneratorstate(my_coro2))# 这里inspect.getgeneratorstate(my_coro2) 得到结果为GEN_CREATED (协程未启动)

next(my_coro2)# 向前执行到第一个yield 处 打印 “-> coroutine started: a = 14”# 并且产生值 14 (yield a执行 等待为b赋值)

print(inspect.getgeneratorstate(my_coro2))# 这里inspect.getgeneratorstate(my_coro2) 得到结果为GEN_SUSPENDED (协程处于暂停状态)

my_coro2.send(28)# 向前执行到第二个yield 处 打印 “-> Received: b = 28”# 并且产生值 a + b = 42(yield a + b 执行 得到结果42 等待为c赋值)

print(inspect.getgeneratorstate(my_coro2))# 这里inspect.getgeneratorstate(my_coro2) 得到结果为GEN_SUSPENDED (协程处于暂停状态)

my_coro2.send(99)# 把数字99发送给暂停协程,计算yield 表达式,得到99,然后把那个数赋值给c 打印 “-> Received: c = 99”# 协程终止,抛出StopIteration

运行上述代码,输出结果如下

GEN_CREATED

-> coroutine started: a = 14GEN_SUSPENDED

-> Received: b = 28

-> Received: c = 99

Traceback (most recent call last):

File "/Users/gs/coroutine.py", line 37, in <module>

my_coro2.send(99)

StopIteration

simple_coro2 协程的执行过程分为3个阶段,如下图所示

1. 调用next(my_coro2),打印第一个消息,然后执行yield a,产出数字14.

2. 调用my_coro2.send(28),把28赋值给b,打印第二个消息,然后执行 yield a + b 产生数字42

3. 调用my_coro2.send(99),把99赋值给c,然后打印第三个消息,协程终止。

使用装饰器预激协程

我们已经知道,协程如果不预激,不能使用send() 传入非None 数据。所以,调用my_coro.send(x)之前,一定要调用next(my_coro)。

为了简化,我们会使用装饰器预激协程。

from functools import wraps

def coroutinue(func):

’’’

装饰器: 向前执行到第一个`yield`表达式,预激`func`

:param func: func name

:return: primer

’’’

@wraps(func)

def primer(*args, **kwargs):

# 把装饰器生成器函数替换成这里的primer函数;调用primer函数时,返回预激后的生成器。

gen = func(*args, **kwargs)

# 调用被被装饰函数,获取生成器对象

next(gen)  # 预激生成器

return gen  # 返回生成器

return primer

# 使用方法如下

@coroutinuedef simple_coro(a):

a = yield

simple_coro(12)  # 已经预激

终止协程和异常处理

协程中,为处理的异常会向上冒泡,传递给next函数或send方法的调用方,未处理的异常会导致协程终止。

看下边这个例子

#! -*- coding: utf-8 -*-

from functools import wraps

def coroutinue(func):

’’’

装饰器: 向前执行到第一个`yield`表达式,预激`func`

:param func: func name

:return: primer

’’’

@wraps(func)

def primer(*args, **kwargs):

# 把装饰器生成器函数替换成这里的primer函数;调用primer函数时,返回预激后的生成器。

gen = func(*args, **kwargs)

# 调用被被装饰函数,获取生成器对象

next(gen)  # 预激生成器

return gen  # 返回生成器

return primer

@coroutinuedef averager():

# 使用协程求平均值

total = 0.0

count = 0

average = None

while True:

term = yield average

total += term

count += 1

average = total/count

coro_avg = averager()

print(coro_avg.send(40))

print(coro_avg.send(50))

print(coro_avg.send(’123’)) # 由于发送的不是数字,导致内部有异常抛出。

执行上述代码结果如下

40.0

45.0

Traceback (most recent call last):

File "/Users/gs/coro_exception.py", line 37, in <module>

print(coro_avg.send(’123’))

File "/Users/gs/coro_exception.py", line 30, in averager

total += term

TypeError: unsupported operand type(s) for +=: ’float’ and ’str’

出错的原因是发送给协程的’123’值不能加到total变量上。

出错后,如果再次调用 coro_avg.send(x) 方法 会抛出 StopIteration 异常。

由上边的例子我们可以知道,如果想让协程退出,可以发送给它一个特定的值。比如None和Ellipsis。(推荐使用Ellipsis,因为我们不太使用这个值)

从Python2.5 开始,我们可以在生成器上调用两个方法,显式的把异常发给协程。

这两个方法是throw和close。

generator.throw(exc_type[, exc_value[, traceback]])

这个方法使生成器在暂停的yield表达式处抛出指定的异常。如果生成器处理了抛出的异常,代码会向前执行到下一个yield表达式,而产出的值会成为调用throw方法得到的返回值。如果没有处理,则向上冒泡,直接抛出。

generator.close()

生成器在暂停的yield表达式处抛出GeneratorExit异常。

如果生成器没有处理这个异常或者抛出了StopIteration异常,调用方不会报错。如果收到GeneratorExit异常,生成器一定不能产出值,否则解释器会抛出RuntimeError异常。

示例: 使用close和throw方法控制协程。

import inspect

class DemoException(Exception):

pass

@coroutinuedef exc_handling():

print(’-> coroutine started’)

while True:

try:

x = yield

except DemoException:

print(’*** DemoException handled. Conginuing...’)

else:

# 如果没有异常显示接收到的值

print(’--> coroutine received: {!r}’.format(x))

raise RuntimeError(’This line should never run.’)  # 这一行永远不会执行

exc_coro = exc_handling()

exc_coro.send(11)

exc_coro.send(12)

exc_coro.send(13)

exc_coro.close()

print(inspect.getgeneratorstate(exc_coro))

raise RuntimeError(’This line should never run.’) 永远不会执行,因为只有未处理的异常才会终止循环,而一旦出现未处理的异常,协程会立即终止。

执行上述代码得到结果为:

-> coroutine started

--> coroutine received: 11

--> coroutine received: 12

--> coroutine received: 13

GEN_CLOSED    # 协程终止

上述代码,如果传入DemoException,协程不会中止,因为做了异常处理。

exc_coro = exc_handling()

exc_coro.send(11)

exc_coro.send(12)

exc_coro.send(13)

exc_coro.throw(DemoException) # 协程不会中止,但是如果传入的是未处理的异常,协程会终止print(inspect.getgeneratorstate(exc_coro))

exc_coro.close()print(inspect.getgeneratorstate(exc_coro))

## output

-> coroutine started

--> coroutine received: 11

--> coroutine received: 12

--> coroutine received: 13

*** DemoException handled. Conginuing...

GEN_SUSPENDED

GEN_CLOSED

如果不管协程如何结束都想做些处理工作,要把协程定义体重的相关代码放入try/finally块中。

@coroutinuedef exc_handling():

print(’-> coroutine started’)

try:

while True:

try:

x = yield

except DemoException:

print(’*** DemoException handled. Conginuing...’)

else:

# 如果没有异常显示接收到的值

print(’--> coroutine received: {!r}’.format(x))

finally:

print(’-> coroutine ending’)

来源:简书

时间: 2024-08-07 17:00:47

python协程:yield的使用的相关文章

00.用 yield 实现 Python 协程

来源:Python与数据分析 链接: https://mp.weixin.qq.com/s/GrU6C-x4K0WBNPYNJBCrMw 什么是协程 引用官方的说法: 协程是一种用户态的轻量级线程,协程的调度完全由用户控制.协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快. 与线程相比,协程更轻量.一个Python线程大概占用8M内

Python 协程总结

Python 协程总结 理解 协程,又称为微线程,看上去像是子程序,但是它和子程序又不太一样,它在执行的过程中,可以在中断当前的子程序后去执行别的子程序,再返回来执行之前的子程序,但是它的相关信息还是之前的. 优点: 极高的执行效率,因为子程序切换而不是线程切换,没有了线程切换的开销: 不需要多线程的锁机制,因为只有一个线程在执行: 如果要充分利用CPU多核,可以通过使用多进程+协程的方式 使用 打开asyncio的源代码,可以发现asyncio中的需要用到的文件如下: 下面的则是接下来要总结的

从python协程理解tornado异步

博客原文地址:http://www.v2steve.com/py_tornado_async.html 刚接触tornado时候最疑惑的问题就是tornado.gen.coroutine是怎么实现的.如何在代码中用同步格式实现异步效果.看了几次源码发现其实就是python协程的一个具体应用.下面从生成器开始,说说tornado的异步. python协程 python利用yield关键字实现生成器,yield就像生化危机里的T病毒,被yield感染的函数都不仅仅是函数,而是一个函数生成器.函数生成

Python核心技术与实战——十五|Python协程

我们在上一章将生成器的时候最后写了,在Python2中生成器还扮演了一个重要的角色——实现Python的协程.那什么是协程呢? 协程 协程是实现并发编程的一种方式.提到并发,肯很多人都会想到多线程/多进程模型,这就是解决并发问题的经典模型之一.在最初的互联网世界中,多线程/多进程就在服务器并发中起到举足轻重的作用. 但是随着互联网的发展,慢慢很多场合都会遇到C10K瓶颈,也就是同时连接到服务器的客户达到1W,于是,很多代码就跑崩溃,因为进程的上下文切换占用了大量的资源,线程也顶不住如此巨大的压力

理解Python协程:从yield/send到yield from再到async/await

Python中的协程大概经历了如下三个阶段: 1. 最初的生成器变形yield/send 2. 引入@asyncio.coroutine和yield from 3. 在最近的Python3.5版本中引入async/await关键字 一.生成器变形yield/send def mygen(alist): while len(alist) > 0: c = randint(0, len(alist)-1) yield alist.pop(c) a = ["aa","bb&q

python协程函数、递归、匿名函数与内置函数使用、模块与包

目录: 协程函数(yield生成器用法二) 面向过程编程 递归 匿名函数与内置函数的使用 模块 包 常用标准模块之re(正则表达式) 一.协程函数(yield生成器用法二) 1.生成器的语句形式 a.生成器相关python函数.装饰器.迭代器.生成器,我们是如何使用生成器的.一个生成器能暂停执行并返回一个中间的结果这就是 yield 语句的功能 : 返回一个中间值给调用者并暂停执行. 我们的调用方式为yeild 1的方式,此方式又称为生成器的语句形式. 而使用生成器的场景:使用生成器最好的场景就

python 协程小程序(草稿有待完善)

#description下面这个小程序就像linux中命tail -f /var/log/messages一样,当运行时可以动态的显示文本文件里的信息哦! import time import sys import os def tail(f): f.seek(0,2) #跳转到文本文件的最后的位置 while True: line = f.readline() if not line: time.sleep(0.1) continue yield line#匹配函数 def grep(line

python 协程, 异步IO Select 和 selectors 模块 多并发演示

主要内容 Gevent协程 Select\Poll\Epoll异步IO与事件驱动 selectors 模块 多并发演示 协程 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈.因此: 协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开

Python 协程(gevent)

协程,又叫微线程,协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈.因此: 协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置. 协程的好处: 无需线程上下文切换的开销 无需原子操作锁定及同步的开销 方便切换控制流,简化编程模型 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问