查看: 212|回复: 3

Python协程实现:yield、asyncio装饰器与async/aw

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
协程的核心机制是通过一个线程实现代码块之间的相互切换执行。当线程遇到 IO 等待时,不会阻塞等待,而是利用空闲时间切换到其他任务继续执行,从而提升并发效率。Python 中实现协程主要有三种方式:yield 生成的生成器、 asyncio 装饰器(Python 3.4 引入)以及 async/await 关键字(Python 3.5 后推荐)。

1. 使用 yield 关键字
当一个函数包含 yield 关键字时,它就是一个生成器函数。调用该函数返回生成器对象,通过迭代可以依次执行 yield 语句。
  1. def func1():
  2.     yield 1
  3.     yield from func2()   # 跳转到 func2 函数内
  4.     yield 2
  5. def func2():
  6.     yield 3
  7.     yield 4
  8. f1 = func1()
  9. for item in f1:
  10.     print(item)
复制代码

上述代码中,yield from 用于委托另一个生成器,实现简单的切换效果。但在实际协程开发中并不直接使用这种模式,仅作为理解协程切换的基础。

2. 使用 asyncio 装饰器(Python 3.4)
Python 3.4 引入了 asyncio 模块,通过 @asyncio.coroutine 装饰器将生成器标记为协程,配合 yield from 实现异步切换。
  1. import asyncio
  2. @asyncio.coroutine
  3. def func1():
  4.     print(1)
  5.     yield from asyncio.sleep(2)   # 遇到 IO 耗时操作,自动切换到其他任务
  6.     print(2)
  7. @asyncio.coroutine
  8. def func2():
  9.     print(3)
  10.     yield from asyncio.sleep(2)
  11.     print(4)
  12. tasks = [asyncio.ensure_future(func1()),
  13.          asyncio.ensure_future(func2())]
  14. loop = asyncio.get_event_loop()
  15. loop.run_until_complete(asyncio.wait(tasks))
复制代码

asyncio.sleep(2) 模拟 IO 等待。当遇到 yield from 后面的阻塞操作时,事件循环会自动将当前协程挂起,切换到 tasks 列表中的其他协程,待 IO 完成后再恢复执行。这种机制可以显著提升 IO 密集型任务的效率,例如下载多张图片时,发送请求后不必等待响应,而是立即发送下一个请求,节省总体时间。

3. 使用 async/await 关键字(Python 3.5+,推荐)
从 Python 3.5 开始,官方推荐使用 async def 定义协程函数,用 await 替代 yield from。
  1. import asyncio
  2. async def func1():
  3.     print(1)
  4.     await asyncio.sleep(2)
  5.     print(2)
  6. async def func2():
  7.     print(3)
  8.     await asyncio.sleep(2)
  9.     print(4)
  10. tasks = [asyncio.ensure_future(func1()),
  11.          asyncio.ensure_future(func2())]
  12. loop = asyncio.get_event_loop()
  13. loop.run_until_complete(asyncio.wait(tasks))
复制代码

这种方式本质与 asyncio 装饰器相同,只是语法更简洁、更直观。async/await 已成为 Python 异步编程的标准写法,在 3.5 之后的版本中应优先采用。

总结:三种方式演进脉络清晰。yield 生成器提供了最原始的协程概念演示,但不适用于实际异步编程。asyncio 装饰器在 3.4 中作为过渡方案,而 async/await 则代表了 Python 协程的现代实践。实际项目开发中,直接使用 asyncio 模块搭配 async/await 即可实现高效的 IO 并发处理。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python协程实现:yield、asyncio装饰器与async/aw

感谢楼主分享这么清晰的总结!三种方式的演进脉络确实很直观,特别是从 yield 的基础理解到 async/await 的现代实践,帮助新人建立整体认知很有价值。 一个小点:在 asyncio 装饰器那段,`asyncio.sleep(2)` 模拟 IO 时,用 `yield from` 来等待,其实和 async/await 里的 `await asyncio.sleep(2)` 效果完全一致,只是语法风格不同。楼主提到“实际项目用 async/await 即可”,非常赞同,现在连 Python 官方文档都逐渐淡化了 coroutine 装饰器的用法。 另外想请教一下:在实际项目中,如果遇到既有 CPU 密集又有 IO 密集的混合场景,楼主一般会怎么处理协程和线程池的配合?比如用 `loop.run_in_executor` 之类的是否推荐?还是说直接拆成两个服务更稳妥?
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python协程实现:yield、asyncio装饰器与async/aw

感谢楼主的详细介绍,把协程的三种实现方式讲得很清楚。从 yield 到 asyncio 装饰器再到 async/await,这个演进过程确实体现了 Python 在异步编程上的逐步优化。实际项目中用 async/await 确实更简洁直观,而且配合 asyncio 的事件循环处理 IO 密集型任务效率很高。不过有个小细节想请教一下:在 Python 3.8+ 中,`asyncio.ensure_future` 也可以直接用 `asyncio.create_task` 替代,后者在某些场景下更推荐,楼主有对比过这两种写法的差异吗?
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python协程实现:yield、asyncio装饰器与async/aw

写得挺清晰的,三种方式的演进脉络讲得很明白。之前一直用yield from做生成器,后来改用async/await确实简洁不少,而且可读性也高。对于IO密集型的爬虫或者网络请求,async/await配合asyncio简直不要太香。有时间可以再讲讲事件循环和Future的内部机制,那就更完整了。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-7-14 12:18 , Processed in 0.024720 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部