4-2

184 阅读4分钟

协程

yield from

yield 和yield from 的区别

def generator_1(titles):
yield titles
def generator_2(titles):
yield from titles

titles = ['Python','Java','C++']
for title in generator_1(titles):
print('生成器1:',title)
for title in generator_2(titles):
print('生成器2:',title)

结果

生成器1: ['Python', 'Java', 'C++']
生成器2: Python
生成器2: Java
生成器2: C++

主要的功能是省去了很多异常的处理,不再需要我们手动编写,其内部已经实现大部分异常处理。

def generator_1():
total = 0
while True:
x = yield 
print('加',x)
if not x:
break
total += x
return total
def generator_2(): # 委托生成器
while True:
total = yield from generator_1() # 子生成器
print('加和总数是:',total)
def main(): # 调用方
g1 = generator_1()
g1.send(None)
g1.send(2)
g1.send(3)
g1.send(None)
# g2 = generator_2()
# g2.send(None)
# g2.send(2)
# g2.send(3)
# g2.send(None)

main()

结果

加 2
加 3
加 None
------------------------------------------
StopIteration   
<ipython-input-37-cf298490352b> in main()
---> 19 g1.send(None)
StopIteration: 5

如果把g1.send()那5行注释掉,解注下面的g2.send()代码,则结果如下。可见yield from封装了处理常见异常的代码。对于g2即便传入None也不报异常,其中total = yield from generator_1()返回给total的值是generator_1()最终的return total

加 2
加 3
加 None
加和总数是: 5

【子生成器】:yield from后的generator_1()生成器函数是子生成器 【委托生成器】:generator_2()是程序中的委托生成器,它负责委托子生成器完成具体任务。

【调用方】:main()是程序中的调用方,负责调用委托生成器。

yield from在其中还有一个关键的作用是:建立调用方和子生成器的通道,

在上述代码中main()每一次在调用send(value)时,value不是传递给了委托生成器generator_2(),而是借助yield from传递给了子生成器generator_1()中的yield 同理,子生成器中的数据也是通过yield直接发送到调用方main()中。

@asyncio.coroutine

# 使用同步方式编写异步功能
import time
import asyncio
@asyncio.coroutine # 标志协程的装饰器
def taskIO_1():
print('开始运行IO任务1...')
yield from asyncio.sleep(2)  # 假设该任务耗时2s
print('IO任务1已完成,耗时2s')
return taskIO_1.__name__
@asyncio.coroutine # 标志协程的装饰器
def taskIO_2():
print('开始运行IO任务2...')
yield from asyncio.sleep(3)  # 假设该任务耗时3s
print('IO任务2已完成,耗时3s')
return taskIO_2.__name__
@asyncio.coroutine # 标志协程的装饰器
def main(): # 调用方
tasks = [taskIO_1(), taskIO_2()]  # 把所有任务添加到task中
done,pending = yield from asyncio.wait(tasks) # 子生成器
for r in done: # done和pending都是一个任务,所以返回结果需要逐个调用result()
print('协程无序返回值:'+r.result())

if __name__ == '__main__':
start = time.time()
loop = asyncio.get_event_loop() # 创建一个事件循环对象loop
try:
loop.run_until_complete(main()) # 完成事件循环,直到最后一个任务结束
finally:
loop.close() # 结束事件循环

print('所有IO任务总耗时%.5f秒' % float(time.time()-start))

【执行过程】:

  1. 上面代码先通过get_event_loop()获取了一个标准事件循环loop(因为是一个,所以协程是单线程)
    
  2. 然后,我们通过run_until_complete(main())来运行协程(此处把调用方协程main()作为参数,调用方负责调用其他委托生成器),run_until_complete的特点就像该函数的名字,直到循环事件的所有事件都处理完才能完整结束。
    
  3. 进入调用方协程,我们把多个任务[taskIO_1()和taskIO_2()]放到一个task列表中,可理解为打包任务。
    
  4. 现在,我们使用asyncio.wait(tasks)来获取一个awaitable objects即可等待对象的集合(此处的aws是协程的列表),并发运行传入的aws,同时通过yield from返回一个包含(done, pending)的元组,done表示已完成的任务列表,pending表示未完成的任务列表;如果使用asyncio.as_completed(tasks)则会按完成顺序生成协程的迭代器(常用于for循环中),因此当你用它迭代时,会尽快得到每个可用的结果。【此外,当轮询到某个事件时(如taskIO_1()),直到遇到该任务中的yield from中断,开始处理下一个事件(如taskIO_2())),当yield from后面的子生成器完成任务时,该事件才再次被唤醒】
    
  5. 因为done里面有我们需要的返回结果,但它目前还是个任务列表,所以要取出返回的结果值,我们遍历它并逐个调用result()取出结果即可。(注:对于asyncio.wait()和asyncio.as_completed()返回的结果均是先完成的任务结果排在前面,所以此时打印出的结果不一定和原始顺序相同,但使用gather()的话可以得到原始顺序的结果集)
    
  6. 最后我们通过loop.close()关闭事件循环