Python上篇:3. Python是如何从yield/send到yield from再到async/await

简介: Python上篇:3. Python是如何从yield/send到yield from再到async/await

Python中的协程大概经历了如下三个阶段:


  1. 最初的生成器变形yield/send
  2. 引入@asyncio.coroutine和yield from
  3. 在最近的Python3.5版本中引入async/await关键字


1. 生成器yield/send



生成器就是一种迭代器,可以使用for进行迭代。生成器函数最大的特点是可以接受外部传入的一个变量,并根据变量内容计算结果后返回。这一切都是靠生成器内部的send()函数实现的。


def gen():
    value=0
    while True:
        receive=yield value
        if receive=='e':
            break
        value = 'got: %s' % receive
g=gen()
print(g.send(None))    
print(g.send('hello'))
print(g.send(123456))
print(g.send('e'))


上面生成器函数中最关键也是最易理解错的,就是receive=yield value这句,如果对循环体的执行步骤理解错误,就会失之毫厘,差之千里。


其实receive=yield value包含了3个步骤:


1、向函数外抛出(返回)value

2、暂停(pause),等待next()或send()恢复

3、赋值receive=MockGetValue() 。这个MockGetValue()是假想函数,用来接收send()发送进来的值


执行流程:


1、通过g.send(None)或者next(g)启动生成器函数,并执行到第一个yield语句结束的位置。这里是关键,很多人就是在这里搞糊涂的。运行receive=yield value语句时,我们按照开始说的拆开来看,实际程序只执行了1,2两步,程序返回了value值,并暂停(pause),并没有执行第3步给receive赋值。因此yield value会输出初始值0。这里要特别注意:在启动生成器函数时只能send(None),如果试图输入其它的值都会得到错误提示信息。

2、通过g.send('hello'),会传入hello,从上次暂停的位置继续执行,那么就是运行第3步,赋值给receive。然后计算出value的值,并回到while头部,遇到yield value,程序再次执行了1,2两步,程序返回了value值,并暂停(pause)。此时yield value会输出”got: hello”,并等待send()激活。

3、通过g.send(123456),会重复第2步,最后输出结果为”got: 123456″。

4、当我们g.send(‘e’)时,程序会执行break然后推出循环,最后整个函数执行完毕,所以会得到StopIteration异常。


从上面可以看出, 在第一次send(None)启动生成器(执行1–>2,通常第一次返回的值没有什么用)之后,对于外部的每一次send(),生成器的实际在循环中的运行顺序是3–>1–>2,也就是先获取值,然后dosomething,然后返回一个值,再暂停等待。


2. yield from



def g1():     
     yield  range(5)
def g2():
     yield  from range(5)
it1 = g1()
it2 = g2()
for x in it1:
    print(x)
for x in it2:
    print(x)


输出:


range(0, 5)  //g1
//g2
0 
1 
2 
3 
4

这说明yield就是将range这个可迭代对象直接返回了。

而yield from解析了range对象,将其中每一个item返回了。

yield from iterable本质上等于for item in iterable: yield item的缩写版。


再强调一遍:yield from后面必须跟iterable对象(可以是生成器,迭代器),(生成器一定是迭代器iterator,迭代器一定是可迭代对象iterable)


3. asyncio.coroutine和yield from



yield from在asyncio模块中得以发扬光大。之前都是我们手工切换协程,现在当声明函数为协程后,我们通过事件循环来调度协程。


先看示例代码:


import asyncio,random
@asyncio.coroutine
def smart_fib(n):
    index = 0
    a = 0
    b = 1
    while index < n:
        sleep_secs = random.uniform(0, 0.2)
        yield from asyncio.sleep(sleep_secs) #通常yield from后都是接的耗时操作
        print('Smart one think {} secs to get {}'.format(sleep_secs, b))
        a, b = b, a + b
        index += 1
@asyncio.coroutine
def stupid_fib(n):
    index = 0
    a = 0
    b = 1
    while index < n:
        sleep_secs = random.uniform(0, 0.4)
        yield from asyncio.sleep(sleep_secs) #通常yield from后都是接的耗时操作
        print('Stupid one think {} secs to get {}'.format(sleep_secs, b))
        a, b = b, a + b
        index += 1
if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    tasks = [
        smart_fib(10),
        stupid_fib(10),
    ]
    loop.run_until_complete(asyncio.wait(tasks))
    print('All fib finished.')
    loop.close()

yield from语法可以让我们方便地调用另一个generator。


本例中yield from后面接的asyncio.sleep()是一个coroutine(里面也用了yield from),所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。


asyncio是一个基于事件循环的实现异步I/O的模块。通过yield from,我们可以将协程asyncio.sleep的控制权交给事件循环,然后挂起当前协程;之后,由事件循环决定何时唤醒asyncio.sleep,接着向后执行代码。协程之间的调度都是由事件循环决定。


yield from asyncio.sleep(sleep_secs) 这里不能用time.sleep(1)因为time.sleep()返回的是None,它不是iterable,还记得前面说的yield from后面必须跟iterable对象(可以是生成器,迭代器)。所以会报错:


yield from time.sleep(sleep_secs) 
TypeError: ‘NoneType’ object is not iterable


4. async和await



python3.5之后就开始原生支持协称了,你只需要在函数开头以async开始,在需要等待的地方await就行,这样就完完全全实现了协称,创建示例如下:


import time,asyncio,random
async def mygen(alist):
    while len(alist) > 0:
        c = random.randint(0, len(alist)-1)
        print(alist.pop(c))
        await asyncio.sleep(1) 
strlist = ["ss","dd","gg"]
intlist=[1,2,5,6]
c1=mygen(strlist)
c2=mygen(intlist)
print(c1)


运行协程,要用事件循环 在上面的代码下面加上:


if __name__ == '__main__':
        loop = asyncio.get_event_loop()
        tasks = [
        c1,
        c2
        ]
        loop.run_until_complete(asyncio.wait(tasks))
        print('All fib finished.')
        loop.close()

就可以看到交替执行的效果:


640.png


5. 小结



这篇文章我们讲解了协称经历的三个阶段,这三个阶段可以说是python支持异步编程必经之路,当所有语言都支持协称的时候,你不支持,很有可能就会失去很大一块市场,所以python为了支持也是再艰难的前行啊。

相关文章
|
10天前
|
开发工具 Python
Python中return和yield的区别,面试官不讲武德
Python中return和yield的区别,面试官不讲武德
|
12天前
|
Python
Python yield 关键字的作用?
Python yield 关键字的作用?
20 0
|
12天前
|
前端开发 Python
探索Python中的异步编程:从回调到async/await
本文将深入探讨Python中的异步编程模式,从最初的回调函数到现代的async/await语法。我们将介绍异步编程的基本概念,探讨其在Python中的实现方式,以及如何使用asyncio库和async/await语法来简化异步代码的编写。通过本文,读者将能够全面了解Python中的异步编程,并掌握使用异步技术构建高效、响应式应用程序的方法。
|
12天前
|
调度 UED Python
探索Python中的异步编程:从回调到async/await
本文将深入探讨Python中的异步编程,从最初的回调函数到现代的async/await语法。通过比较不同的异步编程方法,读者将了解它们的优缺点,并学习如何在项目中选择合适的方式来提高性能和可维护性。
|
12天前
|
开发者 Python
探索Python中的异步编程:从回调到async/await
随着计算机系统的不断发展和多核处理器的普及,异步编程在Python中变得越来越重要。本文将深入探讨Python中异步编程的发展历程,从最初的回调函数到如今的async/await关键字,帮助读者更好地理解和应用异步编程技术。
|
12天前
|
开发者 Python
探索Python中的异步编程:从回调到async/await
传统的同步编程模式在处理I/O密集型任务时可能会面临性能瓶颈,因此异步编程成为了Python开发者的热门选择。本文将从回调函数的基本概念出发,探索Python中异步编程的发展历程,介绍了异步编程的核心概念和常见用法,最终深入讨论了Python 3.5引入的async/await关键字,以及它们如何简化异步代码的编写。
|
12天前
|
大数据 Python
Python中的`yield`:掌握生成器的精髓
【4月更文挑战第17天】`yield`在Python中用于创建生成器,一种节约内存的迭代器。生成器函数在迭代时暂停并保存状态,下次迭代时继续执行,适用于处理大数据、实现协程和优化内存。`yield`不同于普通函数,不立即计算所有结果,而是在需要时生成单个值。使用场景包括生成大列表、实现协程和简化迭代逻辑。注意`yield`后的值不能是表达式,生成器只能调用一次,且`yield`与`return`作用不同。理解并善用`yield`能提升Python编程效率。
|
12天前
|
Python
Python 的异步编程:什么是异步编程?Python 中的 `async` 和 `await` 关键字是用来做什么的?
【4月更文挑战第14天】Python中的异步编程利用`async`和`await`关键字提升并发性能和响应速度。异步函数在等待操作时可暂停,协程是轻量级线程,实现任务间切换。示例展示了如何定义异步函数和协程,以及如何通过`asyncio`库并发执行任务。
15 1
|
5天前
|
存储 算法 安全
Python编程实验六:面向对象应用
Python编程实验六:面向对象应用
23 1
|
5天前
|
Python
Python编程作业五:面向对象编程
Python编程作业五:面向对象编程
21 1