python中和生成器协程相关的yield之最详最强解释,一看就懂(一)
yield是python中一个非常重要的关键词,所有迭代器都是yield实现的,学习python,如果不把这个yield的意思和用法彻底搞清楚,学习python的生成器,协程和异步io的时候,就会彻底懵逼。所以写一篇总结讲讲yield的东西。
分成四块来讲, 这篇先说yield基本用法,后面会重点将yield from的牛逼之处
一, 生成器中使用yield
语法形式:yield <表达式>
这种情况,可以简单的把它理解为 return <表达式>, 每次next调用,会触发生成器从上一次停止的地方开始执行, 直到下一次遇到yield。 如果是第一次next,就是那个从函数开始知道第一个yield;以后每次next,则都是从上次yiled停止的地方开始, 继续执行,直到遇到下一次yiled, 如果一直执行到函数结束,都没有下个yield遇到, 则生成器运行终止,此时next会在生成器运行终止时,抛出StopIteration异常。 比如下面的代码
def generator():
print("gen-0: start")
for i in range(2):
print("gen-%s-a: i=%s" % (i,i))
yield i + 1 # 可简单理解为 return i + 1, 并等待
print("gen-%s-b: i=%s" % (i,i))
print("gen-c")
yield 3 # 可简单理解为 return 3, 并等待
print("gen-d") # 此处继续执行完成后,由于后续在无其它yield语句,调用next的代码会抛出StopIteration异常 try:
g = generator() # 由于generator是一个包含yield关键字的生成器,此处直接调用gengerator()只是返回该生成器实例,实际无任何输出
print('main-0: start')
n = next(g) # next触发生成器执行,由于是第一次触发,所以从generator循环外第2行和第4行的打印会输出,到第5行遇到yield后返回i+1到主程序,第6行不会执行
print('main-1: n=%s' % n)
n = next(g) # next第二次触发生成器执行,由于是第二次触发,上次执行到第5行, 此时从第6行开始执行, 将输出第6行和第4行的打印, 并再次在第5行返回
print('main-2: n=%s' % n)
n = next(g) # next第三次触发生成器执行,这次generator内部for循环已经完成,故执行完跳出循环执行到第8行后返回
print('main-3: n=%s' % n)
n = next(g) # next视图第四次触发生成器执行,从第9行开始执行,但此后generator已经全部执行完成,所以此时next会抛出StopIteration异常,赋值无法完成
print('main-5: n=%s' % n) # 异常产生, 此行将永远不会被执行
except StopIteration:
print('StopIteration n=%s' % n) # 第17行的next将触发异常
上面这段代码执行输出如下, 各位看官自行对照代码和详细注释很容易看懂
main-0: start
gen-0: start
gen-0-a: i=0
main-1: n=1
gen-0-b: i=0
gen-1-a: i=1
main-2: n=2
gen-1-b: i=1
gen-c
main-3: n=3
gen-d
StopIteration n=3
二, 可以接收传入参数值的yield
语法形式:x = yield <表达式>
next方式触发生成器无法传入参数, 如果想触发generator的同时, 传入参数给生成器,是否可以喃?答案是可以的, 这时候需要做两个改变,一是在生成器内增加变量接收该参数, 即本届标题形式;二是外部调用时, 不使用next, 而采用用调用生成器的send方法即可
def coroutine(): # 这个生成器,我们其名coroutine, 意思是协程,协程和生成器都是使用yield来实现, 但本质上不一个概念
print("coroutine: start")
for i in range(2):
print("coroutine-a: i=%s" % i)
x = yield i + 1 # 由send传入的参数值将被赋值给x, 注意i+1的值是被yield用来返回的,不会赋值给x !
print("coroutine-b: i=%s, x=%s" % (i,x)) cr = coroutine() # 创建一个生成器实例
next(cr) # 生成器的第一次触发必须使用next,此时如果试图用send发送参数,将导致异常败,原因是此时生成器还未启动
try:
print("main-a:")
y = cr.send(0) # 调用生成器的send方法, 将10传给生成器, 并触发一次生成器的执行
print("main-b: y=%s" % y)
y = cr.send(1) # 调用生成器的send方法, 将1传给生成器, 并触发一次生成器的执行
print("main-c: y=%s" % y) # 14行的消息发送将导致StopIteration异常产生, 此行将永远不会被执行
except StopIteration:
print("StopIteration")
以下是执行后的输出
coroutine: start
coroutine-a: i=0
main-a:
coroutine-b: i=0, x=0
coroutine-a: i=1
main-b: y=2
coroutine-b: i=1, x=1
StopIteration
简单再解释一下,
1. 第一次执行next时,生成器通过yield i + 1 返回1, 并停在第5行等待被唤醒, 此时赋值动作尚未发生, x的值只是个None, 同时也为执行后面的print。
2. 生成器通过next进行过首次触发后,可以用send发送参数,比如第11行和12行,生成器内部变量先分别被赋值为 0和1, 大家可以对照输出看
3. 由于生成器总共只有两次yield的机会, next消耗一次,第一次send消耗一次,所以第二次send后,虽然不影响执行完参数传递给生成器的动作, 但由于生成器自身找不下一次yield的机会,生成器执行终止。 导致send抛出StopIteration异常
三。生成器本身的返回值
不知道大家注意到没有, 直接类似的cr = coroutine() 的调用,只是产生一个生成器实例,并没执行。而通过yield的返回时, 生成器本身的逻辑实际上都是为走完的, yield英文是退让的意思,除了无限序列的生成器,生成器最终都会执行完成,那么此时如果生成器通过return正常返回一个值,生成器的使用者能获得吗 ?比如下面的代码, 生成器正常结束后return的代码, 使用方如何获得喃 ?比如下面代码如何获取第10行正常终止后的返回值10
def generator():
yield 5
return 10 # 这个返回值如何获得 ? cr = generator()
n = next(cr) # yield 5返回的值可以被获得并赋值给n
答案是在StopIteration异常的处理逻辑中可以获得,方法如下, 只需要在上面代码后面继续加上以下处理
try:
next(cr)
except StopIteration as e: # 通过except ... as 将异常保存在变量e中
rt = e.value # 异常变量e的value值就是生成器通过return返回值
严格意义上讲, python将StopIteration定义为一个异常可能是不得已的事情, 因为这个异常的意思, 实际就是告诉使用者, “我没发生成下一个了, yield次数已经用完了, 我的使命结束了”, 从这个意义上讲, StopIteration也可以归为是正常逻辑, 所以强烈建议所有使用用生成器的地方,都应该要加上StopIteration异常处理。
四,不用next触发生成器执行
生成器的首次触发一定要使用next或者send(None)触发,这个有时候比较麻烦, 有没有不需要写next的情况喃。 答案是肯定的。我们可以把generator放在循环里面,比如把第一个例子的几次next改成一个for循环
def generator():
print("gen-0: start")
for i in range(2):
yield i + 1
yield 3 try:
g = generator()
for n in g: # for循环是通过next触发生成器实现的, 内部会调用next(g)
print(n)
except StopIteration:
print('StopIteration n=%s' % n)
将得到下面的输出, 因为在python里面所有的迭代,包括循环语句,实际底层的实现都是通过生成器搞定的, 所以在for循环里面, 实际上是python内部实现在帮你调用next来触发生成器.和我们在第一例中的显示依次调用next(g)是一样的 可以看到第一次打印了“start”输出, 后面依次输出1,2,3. 但没有StopExeption抛出, 那是因为for的语意是循环次数和实际相同, 所以最后一次next被for内部消化了, 没有暴露出来而已
gen-0: start
1
2
3
四。 再说 x = yield i, 生成器和协程
我们在来看一下上面第二部分的例子, 这里的x = yield i + 1语句, 实际上使函数本身同时具备了生产者和消费的功能, yield i + 1会让每次执行产生一个值, 这是生成者,而 x = yiled又让它可以接收一个send所发送的值。这样看起来同时具备了双重功能, 但这却是一种不好的用法, 应该尽量避免。而是让一个函数功能单一, 要吗作为生产器,要吗作为仅具备消费功能的协程。虽然协程和生成器都是用yield来实现的, 但不应该将二者功能混淆, 这可能会导致一些难以理解的代码,是不推荐这么用的。 推荐的用发是分开,要吗作为生成器,要吗作为协程,不要让一个函数同时兼备二者的功能
第一节是作为生成器的例子
作为协程, 建议的方式是使用 x = (yield), 不要让yiled生成任何值, 仅仅用于接收
def coroutine(): # 这个是协程,消费每次收到的值
print("coroutine: start")
While True:
print("coroutine-a: i=%s" % i)
x = (yield)
print("coroutine-b: x=%s" % x)
下一篇 : python中和生成器协程相关的yield from之最详最强解释,一看就懂(二)
python中和生成器协程相关的yield之最详最强解释,一看就懂(一)的更多相关文章
- python中和生成器协程相关的yield from之最详最强解释,一看就懂(四)
如果认真读过上文的朋友,应该已经明白了yield from实现的底层generator到caller的上传数据通道是什么了.本文重点讲yield from所实现的caller到coroutine的向下 ...
- python中和生成器协程相关yield from之最详最强解释,一看就懂(二)
一. 从列表中yield 语法形式:yield from <可迭代的对象实例> python中的列表是可迭代的, 如果想构造一个生成器逐一产生list中元素,按之前的yield语法,是在 ...
- Python | 详解Python中的协程,为什么说它的底层是生成器?
今天是Python专题的第26篇文章,我们来聊聊Python当中的协程. 我们曾经在golang关于goroutine的文章当中简单介绍过协程的概念,我们再来简单review一下.协程又称为是微线程, ...
- Python中的协程,为什么说它的底层是生成器?
我们曾经在golang关于goroutine的文章当中简单介绍过 协程 的概念,我们再来简单review一下.协程又称为是微线程,英文名是Coroutine.它和线程一样可以调度,但是不同的是线程的启 ...
- Python协程:从yield/send到async/await
这个文章理好了脉落. http://python.jobbole.com/86069/ 我练 习了一番,感受好了很多... Python由于众所周知的GIL的原因,导致其线程无法发挥多核的并行计算能力 ...
- (转)python协程2:yield from 从入门到精通
原文:http://blog.gusibi.com/post/python-coroutine-yield-from/ https://mp.weixin.qq.com/s?__biz=MzAwNjI ...
- python装饰器,迭代器,生成器,协程
python装饰器[1] 首先先明白以下两点 #嵌套函数 def out1(): def inner1(): print(1234) inner1()#当没有加入inner时out()不会打印输出12 ...
- Python异步IO之协程(一):从yield from到async的使用
引言:协程(coroutine)是Python中一直较为难理解的知识,但其在多任务协作中体现的效率又极为的突出.众所周知,Python中执行多任务还可以通过多进程或一个进程中的多线程来执行,但两者之中 ...
- [转载]Python 3.5 协程究竟是个啥
http://blog.rainy.im/2016/03/10/how-the-heck-does-async-await-work-in-python-3-5/ [译] Python 3.5 协程究 ...
随机推荐
- android.support不统一的问题
今天supprt28遇到的问题,由于28还是预览版,还存在一些bug 都是因为如果程序内出现不同的,support或者其他外部引用库的多个版本,Gradle在进行合并的时候会使用本地持有的,最高版本的 ...
- localStorage,sessionStorage的使用
最近因为项目上需要使用到客户端存储,所以稍微研究了一下,以下说说自己的理解和使用经验 1.调用方法相同 各自都包含以下几种操作: //根据key获取对应的值; window.sessionStorag ...
- SQL PLUS的语句执行Commit
oracle 中有个commit,是用来提交事务的.今天发现sql developer和sql plus的数据查询不一样. 如果我们对数据库进行增删改查,在提交sql语句之后,如果不点击commit, ...
- transition: 0.2s all ease;
/* 全部样式 0.2秒 缓动*/ transition: 0.2s all ease;
- WebService Client Generation Error with JDK8
java.lang.AssertionError: org.xml.sax.SAXParseException; systemId: jar:file:/path/to/glassfish/modul ...
- swift语言混编--语言交互的接口
FFI stands for Foreign Function Interface. A foreign function interface is the popular name for the ...
- 扑克牌游戏-华为OJ-C++实现
/*扑克牌游戏大家应该都比較熟悉了.一副牌由54张组成,含3~A.2各4张,小王1张.大王1张.牌面从小到大用例如以下字符和字符串表示(当中.小写joker表示小王,大写JOKER表示大王): 3 4 ...
- 牛客小白D题(STL的迭代器)
「只要我拉动绳线,你就得随之起舞.」 ——泰兹瑞 泰兹瑞来到卡拉德许之后,由于他精湛的神器制造技术,可谓是过的如鱼得水.这次,他为自己打造了一个编辑器,称为威穆(Vei ...
- 【html5】如何让Canvas标签自适应设备
javascript方法: var oC=document.querySelectorAll('canvas')[0];oC.width=document.documentElement.client ...
- Oracle数据库查询优化(上百万级记录如何提高查询速度)
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引.2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引 ...