yield笔记

看到协程时对yield的用法总是理解不够透彻,因此做一些小笔记,方便日后查看。
此处以一个小例子来说明send到底是干嘛用的,例子来自Python协程:从yield/send到async/await:

1、起源:简单的yield生成器

def fib(n):
    index = a = 0
    b = 1
    while index<n:
        yield b
        a, b = b, a+b
        index += 1

#简单调用:
for i in fib(5):
    print(i,end=' ',sep=',')
# 1 1 2 3 5

#相当于
f = fib(5)
for j in range(5):
    print(next(f),end=' ')

在上面的例子中,函数fib(n)相当于一个生成器,for循环每一次调用相当于执行一次next(),最后调用完会遇到StopIteration退出for循环。

2、send是什么?

想要了解send()是什么,就不得不先理解yield表达式了。yield表达式可表示为[res =] yield [expression],从某种程度来说,方括号中的值都是可以省略的,例如若将fib(n)函数的yield n改成yield也不会报错,只是这样fib(5)这个生成器就会返回5个None了,而将yield n改成 s = yield n结果则不会变,只是此时可以通过sendyield表达式传入数值,这个数值即赋值给了s。看看代码更加清晰:

# yield b ==> yield
def fib(n):
    index = a = 0
    b = 1
    while index<n:
        yield
        a, b = b, a+b
        index += 1

for i in fib(5):
    print(i,end=' ')
# None None None None None

# yield b ==> s=yield b
def fib(n):
    index = a = 0
    b = 1
    while index<n:
        s = yield b
        a, b = b, a+b
        index += 1
# 1 1 2 3 5

既然使用了yield表达式后对生成器没有改变,那么他有什么作用呢?要想yield表达式发挥作用,就必须使用send对其进行传值(赋值),利用传入的值可以来实现一些有用的功能,例如下面简单的记录一下日志信息:

import datetime
import time
import random


def fib(n):
    index = a = 0
    b = 1
    while index < n:
        now = yield b
        print(now)
        a, b = b, a+b
        index += 1

f = fib(5)
res = next(f)   #这一步是必须的,此处相当于send(None),在fib函数中此时执行到yield产出值b=1(也即res等于1),并挂起等待send传入值
while True:
    try:
        print(res)
        time.sleep(random.random())
        res = f.send(datetime.datetime.now())
    except:
        print('over')
        break
#输出
1
2017-12-21 14:15:49.296765
1
2017-12-21 14:15:49.583339
2
2017-12-21 14:15:50.492255
3
2017-12-21 14:15:51.006442
5
2017-12-21 14:15:51.113537
over

从上面可以看出,send发送的值都赋值给了yield表达式的左边的now变量了。另外值得注意的一点是,在使用send之前必须先调用一次next(),此处的next相当于send(None)
yield表达式的执行顺序是先yield产生值,然后挂起等待send传入值。也因此输出的结果是先输出fib序列,然后在输出传入值相关的信息。
下面的例子更好的说明了执行步骤,为了更好的说明执行顺序,此处将fib序列的第一个值改成了2:


import datetime
import time
import random


def fib(n):
    index = 0
    a = 2
    b = 3
    while index < n:
        now = yield b
        print(now)
        a, b = b, a+b
        index += 1

f = fib(5)
res = next(f)
n = 1
while n < 2:
    try:
        print(res)
        time.sleep(random.random())
        res = f.send(datetime.datetime.now())
        print(res)
    except:
        print('over')
        break
    n += 1
#此时仅执行了一次send,输出如下
3
2017-12-21 21:23:06.106728
5

上面的两个不同的fib生成结果中第一个3是在预激活协程时yield产生的,在yield表达式右边产生值后,便会挂起等待传入参数并赋值给左侧的变量now。随后send将时间传入赋值给了yield 表达式左边的nownow被赋值后会一直执行到再次yield b生成5,这也是为什么下面的res是5。此时yield 表达式又再次执行到了yield并挂起等待给now赋值(send)的时候。如此循环,直到yield表达式右侧(这里的右侧依旧是一个生成器)的值耗尽,这是再次send时会引发生StopIteration

3、yield from 是何方神圣?

说完了sendyield from又是用来干什么的呢?下面这个例子也许可以对yield from的用法做一些最简单的说明:

def f1():
    for  i in range(5):
        yield i
    for j in 'abc':
        yield j

def f2():
    yield from f1()

# 下面两种调用生成器的结果是一致的
for i in f1():
    print(i,end=' ')
for j in f2():
    print(j,end=' ')
# 0 1 2 3 4 a b c

但是yield from的作用仅仅如此吗?
你见过有返回值的生成器吗?下面这个生成器在终止时(触发StopIteration)会返回一个值。

def func():
    index = 0
    res = 111
    while index < 5:
        s = yield  # ④
        print('s: ', s)  
        index += 1
    return res

def delegate():
    res = yield from func()  # ③         ##⑥
    print('res: ', res)

f = delegate()
f.send(None)     # ①
i = 10
while i < 15:
    try:
        f.send(i)  # ② 此处send的值发送到了子生成器func()中
    except:
        pass
    i += 1
#输出
s:  10
s:  11
s:  12
s:  13
s:  14
res:  111  

从输出结果可以看出3件事:
首先,委派生成器delegate的确从yield from中收到了返回值——res=111,而且这个返回值并非yield的值,而是子生成器函数func的返回值
其次,从输出结果可以看出,send发送的值都传到了子生成器中,也即是从委派生成器delegate传到了yield from表达式中的子生成器func中,这也是输出结果index 10 ... index 14的由来。
最后,委派生成器向子生成器send发送值后,自身会被挂起,直到子生成器函数func触发终止异常(StopIteration)返回值,这个返回值赋值给yield from表达式左边的res,然后委派生成器就会继续执行,这也是为什么,res:111会在最后输出。当我们将while i<15改成while i<12后会看到输出结果没有res输出,这是因为生成器还没有迭代完(while index<5这里需要send5次才会触发异常返回值),还在等待send发送值。

现在,让我们梳理下上面代码的执行顺序:
①预激活子生成器func,此时子生成器在等待传值;
②向委派生成器delegate传值(send);
③委派生成器通过yield from表达式向子生成器func中传(send)值;
④子生成器收到传入的值i后,便会向后执行print('s: ', s)index+=1yield产生值(虽然此处没有生成任何值),最后又回到继续等待传值(send)的状态;
⑤代码中没有⑤因为⑤代表着循环传值这个过程;
终止生成器,这一步非常关键,因为每传入(send)一个值后都会执行index+=1,回到等待传值得状态。因此第一次传值后index=1(需要注意的是预激活时index为0),第四次传值(send(13))后,此时index=4,当第五次传值时index=5此时会触发异常退出while循环,使得子生成器func返回res值,func返回的值又通过yield from表达式赋值给委派生成器的resres收到值后委派生成器终于不再挂起,向下执行,print(res)。完结撒花。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容