Python 进阶:百万「并发」基础之异步编程(中篇)
HackPython 致力于有趣有价值的编程教学
简介
在上一篇,讨论了阻塞 / 非阻塞、同步 / 异步、并发 / 并行等概念,本节主要来讨论一下生成器、yield 以及 yield from 概念并进行简单的使用。
关键概念
Python 中利用了 asyncio 这个标准库作为异步编程框架,而 aysncio 以及其他多数协程库内部都大量使用了生成器,所以先从生成器聊起。
为什么会是生成器?????回想一下生成器的特性,其利用了 yield 关键字做到了随时暂停以及随时执行的能力,而协程从技术实现角度而言,它的作用其实就是一个可以随时暂停会执行的函数。
生成器
生成器与迭代器关系紧密,????其实生成器就是迭代器另一种更优雅的实现方式,其利用了 yield 关键字实现了迭代器的功能,生成器可以迭代式的利用内存空间????,让数据在需要使用时才被载入,这减少内存的消耗,其利用 yield 关键字使用了这个功能,当生成器函数执行过程中遇到 yield 就会被展厅执行,等下次迭代时再次从暂停处继续执行。
为了让生成器可以实现简单的协程,????在 Python 2.5 的时候对生成器的能力进行了增强,此时利用 yield 可以暂停生成器函数的执行返回数据,也可以通过 send () 方法向生成器发送数据,并且还可以利用 throw () 向生成器内抛出异常以实现可随时终止生成器的目的。
yield 的作用直观如下图:
从图中可看出,在一开始调用 simplecoro2 () 方法时,获得的 mycoro2 变量并不是具体的值,而是一个生成器对象,此时调用其 next () 方法进行迭代,next () 方法会让生成器函数执行到 yield 处,到 yield 后就会将紧随在其后的变量返回,接着可以利用 send () 方法将值传递到生成器中,并让暂停的函数继续从暂停处执行????,next () 与 send () 的不同之处在于 next () 并不能向生成器内部传递值而 send () 可以,可以直接使用 send (None) 来实现 next () 方法的效果。从图中也可以看出,next () 与 send () 会获得下一个 yield 返回的值。
顺带一提,for 迭代也调用了迭代器中的__next__方法,next () 内部也是使用了该方法????。
yield from
为了让生成器分成多个子生成器后可以很容易使用 next ()、send ()、throw () 等方法,Python3.3 中引入了 yield from 表达式????,它允许将一个生成器的部分操作委派给另一个生成器。
虽然 yield from 设计的目的是为了让生成器本身可以委派给子生成器,但 yield from 可以向任意可迭代对象进行委派操作????。
yield from iterable 本质其实就是 for item in iterable: yield item,只是写法更优雅了,简单使用如下
In [1]: def gen1():
...: for i in 'abc':
...: yield i
...: for i in range(5):
...: yield i
...:
In [2]: list(gen1())
Out[2]: ['a', 'b', 'c', 0, 1, 2, 3, 4]
In [4]: def gen2():
...: yield from 'abc'
...: yield from range(5)
...:
In [5]: list(gen2())
Out[5]: ['a', 'b', 'c', 0, 1, 2, 3, 4]
上述代码中其实涉及几个概念,其中 gen2 () 方法因为包含了 yield from 表达式,所以被称为????委派生成器,而 yield from 后接着的表达式通常称为????子生成器,上述代码中的 'abc',range (5) 都是子生成器,而调用委派生成器的代码称为????调用方。
此外,yield from 还可以直接将调用方发送的信息直接传递给子生成器,具体可以看下面代码
from collections import namedtuple
Result = namedtuple('Result', 'count average')
# the subgenerator
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield
print('term:', term)
if term is None:
break
total += term
count += 1
average = total / count
return Result(count, average)
# the delegating generator
def grouper(results, key):
while True:
#只有当生成器averager()结束,才会返回结果给results赋值
results[key] = yield from averager()
print('resluts[key]:', results[key])
def report(results):
for key, result in sorted(results.items()):
group, unit = key.split(';')
print('{:2} {:5} averaging {:.2f}{}'.format(result.count, group, result.average, unit))
def main(data):
results = {}
for key, values in data.items():
group = grouper(results, key)
print(type(group))
next(group)
for value in values:
r = group.send(value)
print('r:',r)
print('value:',value)
group.send(None)
report(results)
data = {
'girls;kg':[40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
'girls;m':[1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],
'boys;kg':[39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
'boys;m':[1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],
}
if __name__ == '__main__':
main(data)
在上述代码中,grouper 函数是委托生成器????,averager 函数是子生成器????,而 main () 函数就是调度者????。
在 main () 函数中,首先通过 grouper () 获得对应的生成器对象,然后调用 next () 方法进行初步的迭代,此时会执行到 averager () 的 yield 处????,因为 yield 后没有跟对应的变量,则 yield 返回的值为 None????,该值会有 grouper () 委托生成器直接传递给 main () 调度者,观察变量 r 的打印则可,接着 for 迭代中使用委托生成器的 send () 方法,该方法发送的数据会有委托生成器直接传递给子生成器,即 averager () 函数中 term 的值,上述代码调度的关系如下图:
从图中看出,????调度者使用 send () 方法传递的数据会被委派生成器直接传递给子生成器,而子生成器 yield 的方法数据也被直接传递回调度者,如果子生成器产生 StopIteration 异常则表示子生成器已经迭代完了,此时委派生成器会接收到该异常,从而继续执行 yield from 整个表达式后的其他表达式,这里 grouper () 函数中 yield from 执行完后,就没有逻辑了。
可以看出,委派生成器具有组织多个子生成器的能力,并可以将调度者的信息转手传递给子生成器????。
结尾
在本节中,主要介绍 Python 中生成器、yield 以及 yield from 的概念与使用,在下一篇中,会接着讨论 Python 的 asyncio 框架以及 async/await 原生协程,最后欢迎学习 HackPython 的教学课程并感谢您的阅读与支持。
参考文章
Python 异步编程详解
Python 也能高并发
推荐阅读
-
Python并发编程之学习异步IO框架:asyncio 中篇(十)
-
python基础之并发编程(二)
-
python基础之并发编程(三)
-
python基础之并发编程(一)
-
详解python异步编程之asyncio(百万并发)
-
python进阶 之 面向对象编程基础
-
Python并发编程之学习异步IO框架:asyncio 中篇(十)
-
Python: 进阶系列之五:并发编程:异步IO(asyncio) 协程(coroutine)与任务(task)的使用
-
Python并发编程系列之常用概念剖析:并行 串行 并发 同步 异步 阻塞 非阻塞 进程 线程 协程...
-
Python 进阶:百万「并发」基础之异步编程(中篇)