yield有两个意思,一个是生产,一个是退让,对于Python生成器的yield来说,这两个含义都成立。yield这个关键字,既可以在生成器中产生一个值,传输给调用方,同时也可以从调用方那获取一个值,在生成器内部使用。此外,yield还会作出让步,暂停生成器,让调用方继续工作,直到调用方需要下一个数据时,调用方则陷入等待直到成器提供给调用方所需的数据,如此循环往复。乍一听,有点像多线程,不明白多线程的同学也不要紧张,可以简单的解释一下多线程

解释多线程之前,我们先解释一下进程,进程可以看成是电脑里运行的一个实例,比方说,我运行一个浏览器,是一个进程,运行QQ,同样也是进程,我用浏览器浏览网站,用浏览器听音乐和下载东西,可以看成浏览器这个进程,里面有3个线程同时在为我做浏览网站,播放音乐还有下载文件。而我用QQ和人聊天,同时我又用QQ给人传输文件,同样也是在QQ这个进程中,有两个线程在为我传输聊天内容,同时传输文件。当然,上述说的并不严谨,只是为了好理解,因为对于像浏览器或者QQ这样的进程,每时每秒可能有成百上千的线程在运行,有可能记录日志或者其他

而协程相比于线程,最大的区别在于,协程不需要像线程那样来回的中断切换,也不需要线程的锁机制,因为线程中断或者锁机制都会对性能问题造成影响,所以协程的性能相比于线程,性能有明显的提高,尤其在线程越多的时候,优势越明显

下面用个例子来看一下协程的运作:

def simple_coroutine():
for i in range(3):
x = yield i + 1 # <1>
print("从调用方获取的值:%s" % x) my_coro = simple_coroutine() # <2>
first = next(my_coro) # <3>
for i in range(5): # <4>
try:
y = my_coro.send(i) # <5>
print("从生成器中获取的值:%s" % y)
except StopIteration:
print("生成器的值拉取完毕") # <6>
print("生成器最初获取的值:%s" % first)

  

运行结果:

从调用方获取的值:0
从生成器中获取的值:2
从调用方获取的值:1
从生成器中获取的值:3
从调用方获取的值:2
生成器的值拉取完毕
生成器的值拉取完毕
生成器的值拉取完毕
生成器最初获取的值:1

  

我们先来说一下程序的运行过程,先看程序中<2>处的代码,传统的概念中,我先执行了my_coro = simple_coroutine() 这块代码,所以会理所当然的认为, simple_coroutine() 这个方法要先执行完毕才能接着执行后续的代码,但实际上不是,因为yield会标明这个方法是一个生成器,所以在程序的最初,他不会先执行完毕 simple_coroutine() 方法,而是把my_coro 这个变量声明称一个生成器,跳过simple_coroutine() ,接着执行后续代码

Python将my_coro 声明称一个生成器后,调用了<3>处的next(my_coro) ,这个方法才开始会顺序执行simple_coroutine()方法中的代码,在Python解释器执行simple_coroutine()方法时,遇到yield关键字,生成器将会陷入等待,这时候解释器会跳到生成器之外,也就是<3>之后的代码,我们调用生成器的send()方法,并传输一个值,这时候Python解释器会从外部的代码重新跳回simple_coroutine() 方法,中在之前停留的地方继续执行

他会将外部传来的值赋给x变量,并顺序执行,直到遇到下一个yield,再像之前那样跳出方法外

由于生成器能提供的值有限,所以当simple_coroutine()方法中执行了3次循环,生成器已经没有多余的值可供调用方获取了,所以每次调用生成器的send()方法,都会抛出StopIteration异常

这里有一点要注意,要激活一个生成器,一定要调用next()方法,而不是调用生成器的send()方法,如果直接调用send()方法会报错

协程有四种状态,分别是

GEN_CREATED:等待执行

GEN_RUNNING:解释器执行

GEN_SUSPENDED:在yield表达式处暂停

GEN_CLOSED:执行结束

协程的状态可以用inspect.getgeneratorstate()函数来确定,来看下面的例子:

from inspect import getgeneratorstate
from time import sleep
import threading def get_state(coro):
print("其他线程生成器状态:%s", getgeneratorstate(coro)) # <1> def simple_coroutine():
for i in range(3):
sleep(0.5)
x = yield i + 1 # <1> my_coro = simple_coroutine()
print("生成器初始状态:%s" % getgeneratorstate(my_coro)) # <2>
first = next(my_coro)
for i in range(5):
try:
my_coro.send(i)
print("主线程生成器初始状态:%s" % getgeneratorstate(my_coro)) # <3>
t = threading.Thread(target=get_state, args=(my_coro,))
t.start()
except StopIteration:
print("生成器的值拉取完毕")
print("生成器最后状态:%s" % getgeneratorstate(my_coro)) # <4>

  

执行结果:

生成器初始状态:GEN_CREATED
生成器状态:%s GEN_SUSPENDED
生成器状态:%s GEN_SUSPENDED
生成器的值拉取完毕
生成器的值拉取完毕
生成器的值拉取完毕
生成器最后状态:GEN_CLOSED

    

<2>处,在激活协程之前,协程的状态是GEN_CREATED,而执行next()之后,以及在调用生成器send()之间,我分主线程也就是调用方和多线程去观察协程的状态,结果状态都是GEN_SUSPENDED,也就是协程处于暂停的状态,我原本想用多线程去捕捉协程的运行态,结果即便是多线程捕捉协程也是GEN_SUSPENDED,而GEN_RUNNING也说明,只有带解释器在运行协程的时候,协程的状态才是GEN_RUNNING,最后是GEN_CLOSED,我们拉取完协程的值后,协程的状态就变为执行结束

示例:使用协程计算平均值

我们可以开发一个协程,不断的往协程发送值,并且让协程累计之前的值并计算平均值,如下:

from functools import wraps

def coroutine(func):
@wraps(func)
def primer(*args, **kwargs):
gen = func(*args, **kwargs)
next(gen)
return gen return primer @coroutine # <1>
def averager():
total = .0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total / count try:
coro_avg = averager()
print(coro_avg.send(10))
print(coro_avg.send(20))
print(coro_avg.send(30))
coro_avg.close() # <2>
print(coro_avg.send(40))
except StopIteration:
print("协程已结束")

    

运行结果:

10.0
15.0
20.0
协程已结束

    

在<1>处,我们用一个装饰器来预先激活协程,而不是之后再调用方里执行一个next()函数。然后,我们不断往协程里传10、20、30,而协程不断累计传入的值,并计算所有值的平均值返回给调用方,最后,我们在<2>处调用协程的close()函数,关闭协程,再调用send()方法,会发现抛出StopIteration异常

当发送给协程不是数字,会导致协程内部有异常抛出

for i in range(1, 6):
try:
print(coro_avg.send(i))
if i % 3 == 0:
coro_avg.send('')
except StopIteration:
print("协程已结束")
except TypeError:
print("传入值异常")

  

运行结果:

1.0
1.5
2.0
传入值异常
协程已结束
协程已结束

  

  

我们设置,当i为3的时候,多传入一个空字符串,结果协程抛出类型错误,协程将运行状态改为结束,之后再往协程传值,都抛出StopIteration异常

我们可以让协程处理一些特定的异常,比如:

class DemoException(Exception):  # <1>
pass def demo_exec_handling():
print("coroutine started")
while True:
try:
x = yield # <2>
except DemoException: # <3>
print("DemoException handled")
else:
print("coroutine received:{}".format(x))

  

运行结果

>>> exec_coro = demo_exec_handling()
>>> next(exec_coro)
coroutine started
>>> print(exec_coro.send(1))
coroutine received:1
None
>>> exec_coro.send(2)
coroutine received:2
>>> exec_coro.send(3)
coroutine received:3
>>> exec_coro.throw(DemoException)
DemoException handled
>>> exec_coro.send(4)
coroutine received:4
>>> exec_coro.throw(ZeroDivisionError)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in demo_exec_handling
ZeroDivisionError
>>> exec_coro.send(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

    

<1>处的DemoException是用来测试协程的,首先我们可以看到,当yield右边没有任何式子时,返回给调用方的是一个None对象,其次如果我们调用throw()方法将异常传入协程,因为协程里有关于DemoException的捕捉,所以协程会继续执行,当我们继续传入ZeroDivisionError,则协程结束

让协程返回值

我们可以改造之前的averager()函数,使它可以返回一个对象,对象里有count和average两个属性

from collections import namedtuple

Result = namedtuple("Result", ["count", "average"])

def averager():
total = .0
count = 0
average = None
while True:
term = yield average
if term is None:
break
total += term
count += 1
average = total / count
return Result(count, average)
   运行结果: >>> coro_avg = averager()
>>> next(coro_avg)
>>> coro_avg.send(10)
10.0
>>> coro_avg.send(20)
15.0
>>> coro_avg.send(30)
20.0
>>> coro_avg.send(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: Result(count=3, average=20.0)

    

当我们发送None的时候,协程结束,返回结果,一如既往,生成器会抛出StopIteration异常,异常对象的value属性保存着返回值,为了获取返回值,我们还要再修改一下代码

try:
coro_avg = averager()
next(coro_avg)
coro_avg.send(10)
coro_avg.send(20)
coro_avg.send(30)
coro_avg.send(None)
except StopIteration as exc:
result = exc.value
print(result)

    

运行结果:

Result(count=3, average=20.0)

    

结语:关于协程yield结构这一块,到此暂做结束,下一章会介绍协程的yield from结构,yield from结构会在内部自动捕获StopIteration异常,还会把协程的返回值变成yield from表达式的值,下一章节将会讨论yield from的结构和用法,谢谢大家

Python协程详解(一)的更多相关文章

  1. python协程详解

    目录 python协程详解 一.什么是协程 二.了解协程的过程 1.yield工作原理 2.预激协程的装饰器 3.终止协程和异常处理 4.让协程返回值 5.yield from的使用 6.yield ...

  2. python协程详解,gevent asyncio

    python协程详解,gevent asyncio 新建模板小书匠 #协程的概念 #模块操作协程 # gevent 扩展模块 # asyncio 内置模块 # 基础的语法 1.生成器实现切换 [1] ...

  3. Python协程详解(二)

    上一章,我们介绍了Python的协程,并讲到用yield达到协程的效果,这一章,我们来介绍yield from的结构和作用 我们先来对比下yield和yield from的用法 def first_g ...

  4. Python进程、线程、协程详解

    进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源的管理和分配.任务的调度. ...

  5. PHP协程 详解

    [开源中国] PHP 使用协同程序实现合作多任务 [风雪之隅] 在PHP中使用协程实现多任务调度

  6. Golang 之协程详解

    转自:https://www.cnblogs.com/liang1101/p/7285955.html 一.Golang 线程和协程的区别 备注:需要区分进程.线程(内核级线程).协程(用户级线程)三 ...

  7. Python协程与Go协程的区别二

    写在前面 世界是复杂的,每一种思想都是为了解决某些现实问题而简化成的模型,想解决就得先面对,面对就需要选择角度,角度决定了模型的质量, 喜欢此UP主汤质看本质的哲学科普,其中简洁又不失细节的介绍了人类 ...

  8. Python核心技术与实战——十六|Python协程

    我们在上一章将生成器的时候最后写了,在Python2中生成器还扮演了一个重要的角色——实现Python的协程.那什么是协程呢? 协程 协程是实现并发编程的一种方式.提到并发,肯很多人都会想到多线程/多 ...

  9. 《python开发技术详解》|百度网盘免费下载|Python开发入门篇

    <python开发技术详解>|百度网盘免费下载|Python开发入门篇 提取码:2sby  内容简介 Python是目前最流行的动态脚本语言之一.本书共27章,由浅入深.全面系统地介绍了利 ...

随机推荐

  1. Teradata 认证系列 - 1. TCPP这是个啥

    一看历史,好几年没发帖...正好最近在自学teradata认证(学也不一定学的完,最后也不一定去考,仅仅安慰一下不想碌碌无为的内心) 网上一搜,百度上的中文相关资料简直为0.这个不奇怪,毕竟都没什么人 ...

  2. swift 2特性记录

    swift 团队一直在优化,让大家准备在秋天的时候,迁移到swift2做准备. 一.错误处理 异常处理,不是NSError对象和双指针. 可以使用 throws   来指定方法来抛出一个错误. 调用d ...

  3. Educational Codeforces Round 51 (Rated for Div. 2)

    做了四个题.. A. Vasya And Password 直接特判即可,,为啥泥萌都说难写,,,, 这个子串实际上是忽悠人的,因为每次改一个字符就可以 我靠我居然被hack了???? %……& ...

  4. 【php】

    1 <?php $arr = [ 'a' => 'aaa', ]; $arr2 = $arr; $arr2['a'] = 'ccc'; print_r($arr); print_r($ar ...

  5. n宫格的实现方法

    方法一.table 1.看成多列 <style> .line{ display: table; width: 1024px; clear:both; overflow:auto; /*-- ...

  6. WPF学习一:XAML的资源(Resources)结构

    一个初学者,把知识做个积累,如果有不对的地方,还请高手指出,谢谢! 先看一段代码:(下面是以Window WPF进行讲解,如果是Web 的话就把<Window改为<Page 而如果是 Us ...

  7. WIN7 64位对Excel操作异常

    在本地做Excel导出功能的测试时,报出“检索COM 类工厂中CLSID 为 {00024500-0000-0000-C000-000000000046}的组件时失败”的异常,知道要对Excel进行D ...

  8. 流媒体 6——MPEG电视

    1.电视图像的数据率 1.1 ITU-R BT.601标准数据率 按照奈奎斯特(Nyquist)采样理论,模拟电视信号经过采样(把连续的时间信号变成离散的时间信号)和量化 (把连续的幅度变成离散的幅度 ...

  9. Java分代的思考

    我们都知道,java的gc是基于java的分代前提,不管是CMS还是G1,都是基于分代思想:老年代和新生代 思考一:为什么可以分代? 1.java中对象的生命周期是不一样的,在gc中对应年龄的概念. ...

  10. Springboot端口设置

    application.properties 加入 server.port=80