开头
这篇内容看起来混乱,但其实是在为一个知识点服务:如何实现协程?
在有了明确的目标之后,就可以发现“混乱”都环绕在一个点上:如何手动切换函数的同时,捎上数据。
因能力有限,错漏处望各友不吝赐教。
概念
yield
关键字可以让一个函数秒变生成器:
def generation_alpha():
yield "z"
yield "t"
yield "y"
if __name__ == "__main__":
myAlphas = generation_alpha()
print(dir(myAlphas))
输出结果很长,这里我就只列出关键部分:[..., '__iter__', ..., __next__', ...]
。
这种时候,我们可以对myAlpha进行以下操作:
for alpha in myAlphas:
print(alpha)
或者:
alphaOne = next(myAlphas)
print(alphaOne)
alphaTwo = next(myAlphas)
print(alphaTwo)
alphaThr = next(myAlphas)
print(alphaThr)
二者可以输出同样的结果。
然而,利用next()
方法会带来一些麻烦。复用上边的例子,如果next()
个数多于yield
个数,会带来StopIteration异常:
if __name__ == "__main__":
myAlphas = generation_alpha()
next(myAlphas)
next(myAlphas)
next(myAlphas)
next(myAlphas) # 第四次next()
# 输出:
Traceback (most recent call last):
File "d:\MyCode\DEMO_TEST\name.py", line 40, in <module>
next(myAlphas)
StopIteration
利用for..in...不会有此异常,是因为for一直在等待这个异常,它等到了,所以知道了:“嗯,遍历该停止。”
yield 与 return
yield与return的区别恐怕不必多讲,现在我想说说混合使用它们的情况。就像这样:
def generation_alpha():
yield "z"
yield "t"
return "y"
if __name__ == "__main__":
myAlphas = generation_alpha()
for alpha in myAlphas:
print(alpha)
# 输出:
z
t
看起来字母y被抛弃了。由于for...in...将异常自行处理掉,现下我们并不清楚程序里边究竟做了什么。换next()
试试!
def generation_alpha():
yield "z"
yield "t"
return "y"
if __name__ == "__main__":
myAlphas = generation_alpha()
next(myAlphas)
next(myAlphas)
next(myAlphas)
# 输出:
Traceback (most recent call last):
File "d:\MyCode\DEMO_TEST\name.py", line 39, in <module>
next(myAlphas)
StopIteration: y
较前一次StopIteration比较,它旁边多一个y。其实我们可以通过捕获这个异常来获取generation_alpha()函数return出来的值:
def generation_alpha():
yield "z"
yield "t"
return "y"
if __name__ == "__main__":
myAlphas = generation_alpha()
alphaOne = next(myAlphas)
alphaTwo = next(myAlphas)
try:
next(myAlphas)
except StopIteration as e:
alphaThr = e.value
print(alphaOne, alphaTwo, alphaThr)
# 输出:
z t y
生成器对象
可能这里得说下生成器对象的三个方法:send、close、throw
send方法
想拿到生成器中的数据不一定要for遍历以及next(),事实上send()也是不错的选择,重点在于:send()方法可以向生成器发送消息。如果说next是你的追求者对你一次次道晚安你却爱理不理;那么send就是你同男友甜蜜互动的工具,但这个时候,你得主动些。
我们假设一个场景吧,假设男女生讨论午饭吃点什么的场景。作为单身汉的我试着模拟这段对话:
def boy():
ans1 = yield "男: 午饭吃什么呢?"
print(ans1) # 女生的第一次回答
ans2 = yield "男: 法式沙拉怎么样?"
print(ans2)
ans3 = yield "男: 五...五花肉?"
print(ans3)
ans4 = yield "男: 所以你想吃什么?"
print(ans4)
if __name__ == "__main__":
girl = boy() # 每个女生都应该对她的男友宣誓主权
que1 = girl.send(None) # 女生打电话给男友, 这个时候女生不需要说什么, 男友会主动找话题
print(que1)
que2 = girl.send("女: 随便.")
print(que2)
que3 = girl.send("女: 所以你觉得我胖咯.")
print(que3)
que4 = girl.send("女: 油腻了呢!")
print(que4)
que5 = girl.send("女: 随便.")
print(que5)
输出结果如下:
男: 午饭吃什么呢?
女: 随便.
男: 法式沙拉怎么样?
女: 所以你觉得我胖咯.
男: 五...五花肉?
女: 油腻了呢!
男: 所以你想吃什么?
女: 随便.
Traceback (most recent call last):
File "d:\MyCode\DEMO_TEST\name.py", line 58, in <module>
que5 = girl.send("女: 随便.")
StopIteration
怎么程序抛异常了呢?我猜大概率上是男生崩溃了吧……等一下,我想正经一点!
事实上send()与next()起着相似作用,只是send()多了一个发出消息的功能。这个消息被赋值给yield左边的变量。yield起到的作用与赋值操作并不连贯。每一个send会让生成器停留在最近一个yield语句执行完的地方,直到下一个send将生成器激活往下走。
过程如图上所说的那样。那么能不能发送方发送消息,接收方选择不接受呢?我想一个男生在那儿自言自语并不违法,那么就自然是可以的:
def boy():
# ans1 = yield "男: 午饭吃什么呢?"
# print(ans1) # 女生的第一次回答
yield "男:午饭吃什么呢?"
yield "男: 法式沙拉怎么样?"
yield "男: 五...五花肉?"
yield "男: 所以你想吃什么?"
if __name__ == "__main__":
girl = boy() # 每个女孩都应该对她的男友宣誓主权
que1 = girl.send(None) # 女生打电话给男友, 这个时候女生不需要说什么, 男友会主动找话题
print(que1)
que2 = girl.send("女: 随便.")
print(que2)
que3 = girl.send("女: 所以你觉得我胖咯.")
print(que3)
que4 = girl.send("女: 油腻了呢!")
print(que4)
que5 = girl.send("女: 随便.")
print(que5)
# 输出:
男:午饭吃什么呢?
男: 法式沙拉怎么样?
男: 五...五花肉?
男: 所以你想吃什么?
Traceback (most recent call last):
File "d:\MyCode\DEMO_TEST\name.py", line 53, in <module>
que5 = girl.send("女: 随便.")
StopIteration
为男女平等起见,女孩也可以自言自语呢!
def boy():
ans1 = yield
print(ans1)
ans2 = yield
print(ans2)
ans3 = yield
print(ans3)
ans4 = yield
print(ans4)
if __name__ == "__main__":
girl = boy()
que1 = girl.send(None)
que2 = girl.send("女: 你怎么不问我午饭吃什么?.")
que3 = girl.send("女: 喂,说话呀!")
que4 = girl.send("女: 死哪儿去了???")
que5 = girl.send("女: 分手!!!")
# 输出:
女: 你怎么不问我午饭吃什么?.
女: 喂,说话呀!
女: 死哪儿去了???
女: 分手!!!
Traceback (most recent call last):
File "d:\MyCode\DEMO_TEST\name.py", line 21, in <module>
que5 = girl.send("女: 分手!!!")
StopIteration
差不多可以总结一下:
- send是先将消息发送给上一个yield,然后接收下一个yield出来的数据。
- send也可以不接受数据或认为接收的数据为None。
- 第一个send发送的内容必须是None,因为它并不存在“上一个yield”。
- yield方可以选择不接受send发来的消息。
- 如果yield个数与send个数保持一致,那么最后一个yield对左边变量的赋值操作将不会执行。
close
close方法可以关闭生成器的入口,这时候如果继续next,会引发StopIteration异常。比如:
def generation_alpha():
yield "z"
yield "t"
yield "y"
if __name__ == "__main__":
myAlphas = generation_alpha()
print(next(myAlphas))
myAlphas.close() # 关闭生成器
next(myAlphas)
# 输出:
z
Traceback (most recent call last):
File "d:\MyCode\DEMO_TEST\name.py", line 13, in <module>
next(myAlphas)
StopIteration
事实上,close方法是让生成器在挂起的地方引发GeneratorExit异常(这个异常继承自BaseException而不是Exception),如果在生成器中捕获这个异常同时想忽略它,从而继续执行后面的yield,解释器会说:“想得美!”
def generation_alpha():
try:
yield "z"
except GeneratorExit:
pass
yield "t"
yield "y"
if __name__ == "__main__":
myAlphas = generation_alpha()
print(next(myAlphas))
myAlphas.close()
# 输出:
z # 注意,仍可以打印出字母z
Traceback (most recent call last):
File "d:\MyCode\DEMO_TEST\name.py", line 17, in <module>
myAlphas.close()
RuntimeError: generator ignored GeneratorExit # 异常变成了RuntimeError
只是要求不可以有yield,你依然能做其他的逻辑处理。
def generation_alpha():
try:
yield "z"
except GeneratorExit:
pass
print("不好意思,我挂掉了!")
if __name__ == "__main__":
myAlphas = generation_alpha()
print(next(myAlphas))
myAlphas.close()
# 输出:
z
不好意思,我挂掉了!
注:我尝试在后面的语句中添加return,然后去获取return出来的值,却始终拿不到。不知道各位有没有什么好法子。
throw
throw也是在生成器挂起的地方起作用,向生成器抛出一个异常,或许你可以这样用:
def generation_alpha():
yield "z"
yield 2
yield "y"
if __name__ == "__main__":
myAlphas = generation_alpha()
for alpha in myAlphas:
if not isinstance(alpha, str):
myAlphas.throw(TypeError, "need str but int")
print(alpha)
# 输出:
z
Traceback (most recent call last):
File "d:\MyCode\DEMO_TEST\name.py", line 13, in <module>
myAlphas.throw(TypeError, "need str but int")
File "d:\MyCode\DEMO_TEST\name.py", line 5, in generation_alpha
yield 2
TypeError: need str but int
yield from
yield from
是python3.3添加的特性。
yield from [iterable]
# 等同于
for i in [iterable]:
yield i
同时yield from
默认处理掉StopIteration异常。
def generation_alpha(): # 子生成器
yield "z"
yield "t"
yield "y"
return "no more"
def generation(): # 委托生成器
flag = yield from generation_alpha()
print(flag)
if __name__ == "__main__":
mygen = generation()
for i in mygen:
print(i)
# 输出:
z
t
y
no more
如开头所说的那样,for...in...会处理掉StopIteration,这种时候如果想拿到generation_alpha()中return出来的值,就不得不try一下(前边也讲述了这个方法)。然而,当你用上yield from
之后,它负责帮你完成这个动作,区别在于:yield from并不负责帮你把StopIteration隐藏起来,而是转移了StopIteration的引发地(尽管这种说法似乎不够准确,但我实在想不到更好的阐述方式)。把for...in...换作next可见真知:
...
if __name__ == "__main__":
mygen = generation()
print(next(mygen))
print(next(mygen))
print(next(mygen))
print(next(mygen)) # 注意第四个next
# 输出:
z
t
y
no more
Traceback (most recent call last):
File "d:\MyCode\DEMO_TEST\name.py", line 19, in <module>
print(next(mygen))
StopIteration
no more被打印出来,这说明flag确实拿到了子生成器return出来的值,但为什么依然抛异常了呢?
这是因为第三次使用next之后,生成器停在了yield "y"
处,之后被第四个next激活,子生成器发现后面居然是return,那怎么行,我要抛异常了!却被委托生成器的yield from
制止住。它们要私了,yield from提议说:“return出来的东西给我吧,以后谁找你要yield都让他来找我。”显然,上面的委托生成器并没有yield,所以异常屁颠屁颠地跑出来了。但要分清,这个StopIteration是从委托生成器跑出来的。
如果你对上述过程心存疑虑,不妨在之前的示例中的委托生成器里加上一个yield试试,就像下面这样:
...
def generation():
flag = yield from generation_alpha()
print(flag)
yield "g"
...
因此在generation()(委托生成器)中使用yield from
等效于以下方式:
...
def generation():
alpha = generation_alpha()
yield next(alpha)
yield next(alpha)
yield next(alpha)
# 等同于: flag = yield from alpha
try:
yield next(alpha)
except StopIteration as e:
flag = e.value
print(flag)
...
却不是完全相等。在我打断点调式的时候发现,通过yield from
之后,主函数与子生成器直接交流,在到达return语句之前都没有委托生成器的事儿。
感谢
- 参考慕课Bobby老师课程Python高级编程和异步IO并发编程
- 参考网上许多帖子,不便一一罗列了
- 感谢stackoverflow上光速回答我提问的那位兄弟
还不快抢沙发