欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python 进阶:百万「并发」基础之异步编程(中篇)

程序员文章站 2022-05-08 08:37:45
...

HackPython 致力于有趣有价值的编程教学

Python 进阶:百万「并发」基础之异步编程(中篇)

简介

在上一篇,讨论了阻塞 / 非阻塞、同步 / 异步、并发 / 并行等概念,本节主要来讨论一下生成器、yield 以及 yield from 概念并进行简单的使用。

关键概念

Python 中利用了 asyncio 这个标准库作为异步编程框架,而 aysncio 以及其他多数协程库内部都大量使用了生成器,所以先从生成器聊起。

为什么会是生成器?????回想一下生成器的特性,其利用了 yield 关键字做到了随时暂停以及随时执行的能力,而协程从技术实现角度而言,它的作用其实就是一个可以随时暂停会执行的函数。

生成器

生成器与迭代器关系紧密,????其实生成器就是迭代器另一种更优雅的实现方式,其利用了 yield 关键字实现了迭代器的功能,生成器可以迭代式的利用内存空间????,让数据在需要使用时才被载入,这减少内存的消耗,其利用 yield 关键字使用了这个功能,当生成器函数执行过程中遇到 yield 就会被展厅执行,等下次迭代时再次从暂停处继续执行。

为了让生成器可以实现简单的协程,????在 Python 2.5 的时候对生成器的能力进行了增强,此时利用 yield 可以暂停生成器函数的执行返回数据,也可以通过 send () 方法向生成器发送数据,并且还可以利用 throw () 向生成器内抛出异常以实现可随时终止生成器的目的。

yield 的作用直观如下图:

Python 进阶:百万「并发」基础之异步编程(中篇)

从图中可看出,在一开始调用 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 的值,上述代码调度的关系如下图:

Python 进阶:百万「并发」基础之异步编程(中篇)

从图中看出,????调度者使用 send () 方法传递的数据会被委派生成器直接传递给子生成器,而子生成器 yield 的方法数据也被直接传递回调度者,如果子生成器产生 StopIteration 异常则表示子生成器已经迭代完了,此时委派生成器会接收到该异常,从而继续执行 yield from 整个表达式后的其他表达式,这里 grouper () 函数中 yield from 执行完后,就没有逻辑了。

可以看出,委派生成器具有组织多个子生成器的能力,并可以将调度者的信息转手传递给子生成器????。

结尾

在本节中,主要介绍 Python 中生成器、yield 以及 yield from 的概念与使用,在下一篇中,会接着讨论 Python 的 asyncio 框架以及 async/await 原生协程,最后欢迎学习 HackPython 的教学课程并感谢您的阅读与支持。

参考文章

Python 异步编程详解

Python 也能高并发