Python协程详解:从生成器到async/await的完整指南

1. 什么是协程?

协程(Coroutine)是Python中一种特殊的函数,它可以在执行过程中暂停,并在需要时恢复执行。与普通函数不同,协程可以在多个入口点暂停和恢复,这使得它非常适合处理I/O密集型任务和并发编程。

简单来说,协程就像是"可以暂停的函数",它允许我们在函数执行到一半时挂起,去做其他事情,然后再回来继续执行。

2. 协程的发展历程

2.1 生成器(Generator)时代

Python 2.5引入了生成器表达式和yield语句,这是协程的雏形。通过yield,函数可以暂停执行并返回一个值,下次调用时从暂停处继续执行。

python 复制代码
def simple_generator():
    print("开始")
    yield 1
    print("继续")
    yield 2
    print("结束")

gen = simple_generator()
print(next(gen))  # 输出:开始,然后 1
print(next(gen))  # 输出:继续,然后 2

2.2 基于生成器的协程

Python 3.3引入了yield from语法,使得生成器可以委托给其他生成器,这为协程提供了更好的支持。

python 复制代码
def generator_a():
    yield from [1, 2, 3]

def generator_b():
    yield from generator_a()
    yield from [4, 5, 6]

for value in generator_b():
    print(value)  # 输出:1, 2, 3, 4, 5, 6

2.3 async/await时代

Python 3.5引入了asyncawait关键字,这是现代协程的标准写法。async def用于定义协程函数,await用于等待协程执行完成。

python 复制代码
import asyncio

async def say_hello():
    print("Hello")
    await asyncio.sleep(1)
    print("World")

# 运行协程
asyncio.run(say_hello())

3. 协程的核心概念

3.1 协程函数 vs 协程对象

  • 协程函数 :使用async def定义的函数
  • 协程对象:调用协程函数返回的对象
python 复制代码
async def my_coroutine():
    return 42

# my_coroutine 是协程函数
coro = my_coroutine()  # coro 是协程对象

3.2 事件循环(Event Loop)

事件循环是异步编程的核心,它负责调度和执行协程。在Python中,asyncio模块提供了事件循环的实现。

python 复制代码
import asyncio

async def main():
    print("开始")
    await asyncio.sleep(1)
    print("结束")

# 创建事件循环并运行协程
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

3.3 可等待对象(Awaitable)

可等待对象是可以在await表达式中使用的对象,包括:

  • 协程(Coroutine)
  • 任务(Task)
  • Future对象

4. 使用async/await编写协程

4.1 基本用法

python 复制代码
import asyncio

async def fetch_data():
    print("开始获取数据")
    await asyncio.sleep(2)  # 模拟网络请求
    print("数据获取完成")
    return {"data": "some data"}

async def process_data():
    print("开始处理数据")
    await asyncio.sleep(1)  # 模拟数据处理
    print("数据处理完成")
    return "processed"

async def main():
    # 顺序执行
    data = await fetch_data()
    result = await process_data()
    print(f"数据: {data}, 结果: {result}")

asyncio.run(main())

4.2 并发执行多个协程

python 复制代码
import asyncio

async def task(name, seconds):
    print(f"任务 {name} 开始")
    await asyncio.sleep(seconds)
    print(f"任务 {name} 完成")
    return f"任务 {name} 的结果"

async def main():
    # 使用asyncio.gather并发执行多个协程
    results = await asyncio.gather(
        task("A", 2),
        task("B", 1),
        task("C", 3)
    )
    print(f"所有任务完成: {results}")

asyncio.run(main())

4.3 创建任务(Task)

任务是对协程的进一步封装,它可以在事件循环中调度执行。

python 复制代码
import asyncio

async def long_running_task():
    print("长时间任务开始")
    await asyncio.sleep(5)
    print("长时间任务完成")
    return "任务结果"

async def main():
    # 创建任务
    task1 = asyncio.create_task(long_running_task())
    
    # 在等待任务完成的同时可以做其他事情
    print("主程序继续执行...")
    await asyncio.sleep(1)
    print("检查任务状态...")
    
    # 等待任务完成
    result = await task1
    print(f"任务结果: {result}")

asyncio.run(main())

5. 协程的实际应用场景

5.1 网络请求

python 复制代码
import aiohttp
import asyncio

async def fetch_url(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    async with aiohttp.ClientSession() as session:
        urls = [
            "https://httpbin.org/get",
            "https://httpbin.org/ip",
            "https://httpbin.org/user-agent"
        ]
        
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        
        for url, content in zip(urls, results):
            print(f"{url}: {len(content)} 字符")

asyncio.run(main())

5.2 数据库操作

python 复制代码
import asyncio
import asyncpg

async def database_operations():
    # 连接数据库
    conn = await asyncpg.connect(
        user='user',
        password='password',
        database='database',
        host='localhost'
    )
    
    # 执行查询
    rows = await conn.fetch('SELECT * FROM users WHERE age > $1', 18)
    
    for row in rows:
        print(f"用户: {row['name']}, 年龄: {row['age']}")
    
    # 关闭连接
    await conn.close()

asyncio.run(database_operations())

5.3 Web服务器

python 复制代码
from aiohttp import web
import asyncio

async def handle_request(request):
    name = request.match_info.get('name', "World")
    text = f"Hello, {name}!"
    return web.Response(text=text)

async def background_task(app):
    while True:
        print("后台任务运行中...")
        await asyncio.sleep(5)

async def init_app():
    app = web.Application()
    app.router.add_get('/', handle_request)
    app.router.add_get('/{name}', handle_request)
    
    # 启动后台任务
    app.on_startup.append(lambda app: asyncio.create_task(background_task(app)))
    
    return app

web.run_app(init_app(), port=8080)

6. 协程的注意事项和最佳实践

6.1 避免阻塞操作

在协程中执行阻塞操作(如time.sleep()、同步文件IO等)会阻塞整个事件循环。

python 复制代码
# ❌ 错误示例:在协程中使用同步sleep
async def bad_example():
    import time
    time.sleep(5)  # 这会阻塞事件循环!

# ✅ 正确示例:使用异步sleep
async def good_example():
    await asyncio.sleep(5)  # 这不会阻塞事件循环

6.2 错误处理

python 复制代码
import asyncio

async def risky_operation():
    await asyncio.sleep(1)
    raise ValueError("出错了!")

async def main():
    try:
        await risky_operation()
    except ValueError as e:
        print(f"捕获到错误: {e}")
    
    # 或者使用asyncio.gather的return_exceptions参数
    results = await asyncio.gather(
        risky_operation(),
        return_exceptions=True
    )
    
    for result in results:
        if isinstance(result, Exception):
            print(f"任务出错: {result}")
        else:
            print(f"任务成功: {result}")

asyncio.run(main())

6.3 超时控制

python 复制代码
import asyncio

async def slow_operation():
    await asyncio.sleep(10)
    return "完成"

async def main():
    try:
        # 设置5秒超时
        result = await asyncio.wait_for(slow_operation(), timeout=5)
        print(f"结果: {result}")
    except asyncio.TimeoutError:
        print("操作超时!")

asyncio.run(main())

7. 协程与线程、进程的对比

特性 协程 线程 进程
创建开销 很小 中等 很大
切换开销 很小 中等 很大
内存占用 很小 中等 很大
并发类型 协作式 抢占式 抢占式
数据共享 共享内存 共享内存 需要IPC
适用场景 I/O密集型 I/O密集型、CPU密集型 CPU密集型

8. 总结

Python协程是现代异步编程的核心,它通过async/await语法提供了简洁高效的并发编程方式。协程特别适合处理I/O密集型任务,如网络请求、数据库操作等。

关键要点:

  1. 使用async def定义协程函数,使用await等待协程完成
  2. 事件循环是协程调度的核心
  3. 使用asyncio.gather()并发执行多个协程
  4. 避免在协程中使用阻塞操作
  5. 合理处理协程中的错误和超时

随着Python异步生态的不断完善,协程已经成为构建高性能网络应用的重要工具。掌握协程的使用,能够让你编写出更高效、更易维护的并发代码。

相关推荐
奶茶树1 小时前
【C++】13. C++11新特性【上】
c语言·开发语言·c++·git·github
hhzz1 小时前
智慧校园13类视频异常检测:数据集与预训练模型全攻略
人工智能·pytorch·python·深度学习·目标检测·机器学习
SomeB1oody1 小时前
【RustyML入门】7.4. 按需裁剪与模块化集成
开发语言·后端·机器学习·rust·教程
浩腾数字多媒体1 小时前
如何判断专业电子留言厂家适配条件?
大数据·人工智能·python
会编程的吕洞宾2 小时前
DeepAgents In Action学习(Third)
python·学习·django
'2062 小时前
**写了个自动整理下载视频的脚本:按片名归档、自动重命名、生成目录索引
开发语言·python·ffmpeg·音视频·cctv
xys_6782 小时前
深挖C语言:动态内存管理
c语言·开发语言
码云数智-园园2 小时前
Python如何将律师的Excel噩梦变成自动化系统
python·自动化·excel
belldeep2 小时前
python:Selenium 4.47 编码新的写法
python·selenium