Python协程与生成器:深入解析yield from
与async/await
大家好,今天我们来深入探讨Python中的协程和生成器,特别是yield from
和async/await
这两个关键特性。我们将从生成器开始,逐步过渡到协程,并剖析它们背后的机制。
1. 生成器:迭代器的进化
在理解协程之前,我们必须先掌握生成器的概念。生成器是一种特殊的迭代器,它使用yield
语句来产生值,而不是使用return
语句。
1.1 生成器函数与生成器对象
一个包含yield
语句的函数被称为生成器函数。调用生成器函数不会立即执行函数体,而是返回一个生成器对象。
def my_generator(n):
for i in range(n):
yield i
gen = my_generator(3)
print(gen) # 输出: <generator object my_generator at 0x...>
1.2 生成器的工作方式
生成器对象通过next()
函数(或者在for
循环中使用)来逐个产生值。每次调用next()
,生成器函数会执行到下一个yield
语句,产生一个值并暂停。下次调用next()
时,从上次暂停的地方继续执行。当函数执行完毕或者遇到return
语句时,会抛出StopIteration
异常。
print(next(gen)) # 输出: 0
print(next(gen)) # 输出: 1
print(next(gen)) # 输出: 2
try:
print(next(gen))
except StopIteration:
print("Iteration finished") # 输出: Iteration finished
1.3 生成器的优势
生成器的主要优势在于惰性求值。它们只在需要时才产生值,而不是一次性生成所有值。这可以节省大量内存,尤其是在处理大型数据集时。
def infinite_sequence():
num = 0
while True:
yield num
num += 1
# 使用生成器而不必担心内存溢出
for i in infinite_sequence():
if i > 10:
break
print(i)
2. yield from
:连接生成器的桥梁
yield from
是 Python 3.3 引入的语法糖,它允许我们将一个生成器的产生过程委托给另一个生成器。
2.1 yield from
的基本用法
def sub_generator(n):
for i in range(n):
yield i
def main_generator(n):
yield from sub_generator(n)
yield "Done"
for i in main_generator(3):
print(i)
# 输出:
# 0
# 1
# 2
# Done
在这个例子中,main_generator
使用 yield from
将产生值的任务委托给了 sub_generator
。main_generator
实际上并没有直接产生 0, 1, 2 这些值,而是由 sub_generator
产生的。
2.2 yield from
的等价形式
yield from iterable
实际上可以展开成以下代码:
def equivalent_yield_from(iterable):
it = iter(iterable)
try:
while True:
yield next(it)
except StopIteration:
pass
这段代码首先获取可迭代对象 iterable
的迭代器。然后,它不断地从迭代器中获取下一个值,并使用 yield
产生该值。当迭代器抛出 StopIteration
异常时,循环结束。
2.3 yield from
的高级用法:双向通道
yield from
不仅仅是简单的委托,它还创建了一个双向通道,允许调用者和子生成器之间进行通信。这意味着调用者可以向子生成器发送值(使用 send()
方法),也可以从子生成器接收值。
2.3.1 send()
方法
生成器对象有一个 send()
方法,可以向生成器发送一个值。这个值会成为 yield
表达式的结果。
def echo_generator():
while True:
received = yield
print("Received:", received)
gen = echo_generator()
next(gen) # 启动生成器 (第一次必须调用 next() 或者 send(None))
gen.send("Hello") # 输出: Received: Hello
gen.send("World") # 输出: Received: World
2.3.2 throw()
方法
生成器对象还有一个 throw()
方法,可以向生成器抛出一个异常。
2.3.3 close()
方法
生成器对象还有一个 close()
方法,可以关闭生成器。
2.3.4 双向通道示例
def sub_generator():
x = yield
print("Subgenerator received:", x)
return "Subgenerator done"
def main_generator():
result = yield from sub_generator()
print("Main generator received:", result)
gen = main_generator()
next(gen) # 启动生成器
gen.send(10) # Subgenerator received: 10
# Main generator received: Subgenerator done
在这个例子中,main_generator
使用 yield from
调用 sub_generator
。main_generator
通过 send(10)
将值 10 发送给 sub_generator
。sub_generator
接收到值后,打印出来,并返回 "Subgenerator done"。这个返回值会成为 yield from
表达式的结果,并被赋值给 result
。最后,main_generator
打印出 "Main generator received: Subgenerator done"。
2.4 yield from
的完整语义
为了更清晰地理解 yield from
的工作方式,我们可以将其展开成更详细的代码:
def yield_from_expanded(gen):
"""
等价于 yield from gen() 的展开形式
"""
_i = iter(gen) # 获取迭代器
try:
_y = next(_i) # 预激子生成器
except StopIteration as _e:
_r = _e.value # 获取子生成器的返回值
else:
while 1:
try:
_s = yield _y # 产生值并接收调用者的值
except GeneratorExit as _e:
try:
_m = _i.close
except AttributeError:
pass
else:
_m()
raise _e
except BaseException as _e:
_x = sys.exc_info()
try:
_m = _i.throw
except AttributeError:
raise _e
else:
try:
_y = _m(*_x)
except StopIteration as _e:
_r = _e.value
break
else:
try:
if _s is None:
_y = next(_i)
else:
_y = _i.send(_s)
except StopIteration as _e:
_r = _e.value
break
return _r
这段代码展示了 yield from
如何处理异常、发送值和接收返回值。它完整地模拟了 yield from
的行为。
3. 协程:并发的基石
协程是一种更高级的生成器,它允许在多个函数之间交替执行,从而实现并发。在 Python 中,协程通常使用 async/await
语法来定义。
3.1 async/await
语法
async
关键字用于声明一个协程函数。await
关键字用于暂停协程的执行,等待另一个协程完成。
import asyncio
async def my_coroutine(n):
print(f"Coroutine {n} started")
await asyncio.sleep(1) # 模拟耗时操作
print(f"Coroutine {n} finished")
return f"Result from coroutine {n}"
async def main():
task1 = asyncio.create_task(my_coroutine(1))
task2 = asyncio.create_task(my_coroutine(2))
result1 = await task1
result2 = await task2
print(result1)
print(result2)
asyncio.run(main())
# 可能的输出:
# Coroutine 1 started
# Coroutine 2 started
# (等待 1 秒)
# Coroutine 1 finished
# Coroutine 2 finished
# Result from coroutine 1
# Result from coroutine 2
在这个例子中,my_coroutine
是一个协程函数。await asyncio.sleep(1)
会暂停 my_coroutine
的执行,并将控制权交还给事件循环。事件循环会继续执行其他协程,直到 asyncio.sleep(1)
完成。当 asyncio.sleep(1)
完成后,my_coroutine
会恢复执行。
3.2 事件循环
事件循环是协程的核心。它负责调度协程的执行,处理 I/O 事件,以及执行其他任务。asyncio.run()
函数会创建一个事件循环,并运行指定的协程。
3.3 await
的底层机制
await
关键字实际上是一个语法糖。它背后做了以下几件事情:
- 检查
await
后面的表达式是否是一个 awaitable 对象。Awaitable 对象是指实现了__await__()
方法的对象。 - 调用 awaitable 对象的
__await__()
方法,返回一个迭代器。 - 将当前协程暂停,并将控制权交还给事件循环。
- 当 awaitable 对象完成时,事件循环会恢复当前协程的执行,并获取 awaitable 对象的返回值。
3.4 协程与生成器的关系
协程本质上是一种特殊的生成器。async
声明的函数实际上返回一个协程对象,而协程对象实现了 __await__()
方法,使其成为一个 awaitable 对象。await
关键字实际上是 yield from
的一种变体,它用于等待一个 awaitable 对象完成。
3.5 手动实现 await
为了更好地理解 await
的底层机制,我们可以手动实现一个简单的 await
函数:
def manual_await(awaitable):
"""
手动实现 await 的功能
"""
iterator = awaitable.__await__()
try:
next(iterator)
except StopIteration as e:
return e.value
这个函数首先获取 awaitable 对象的迭代器。然后,它调用 next()
函数来启动迭代器。当迭代器抛出 StopIteration
异常时,函数返回迭代器的返回值。
3.6 异步迭代器和异步上下文管理器
Python 3.5 引入了异步迭代器和异步上下文管理器,它们分别使用 async for
和 async with
语句来处理。
3.6.1 异步迭代器
异步迭代器是指实现了 __aiter__()
和 __anext__()
方法的迭代器。__aiter__()
方法返回异步迭代器本身,__anext__()
方法返回一个 awaitable 对象,该对象产生下一个值。
class AsyncIterator:
def __init__(self, data):
self.data = data
self.index = 0
async def __aiter__(self):
return self
async def __anext__(self):
if self.index >= len(self.data):
raise StopAsyncIteration
value = self.data[self.index]
self.index += 1
return value
async def main():
async for item in AsyncIterator([1, 2, 3]):
print(item)
asyncio.run(main())
3.6.2 异步上下文管理器
异步上下文管理器是指实现了 __aenter__()
和 __aexit__()
方法的上下文管理器。__aenter__()
方法返回一个 awaitable 对象,该对象在进入上下文时执行。__aexit__()
方法返回一个 awaitable 对象,该对象在退出上下文时执行。
class AsyncContextManager:
async def __aenter__(self):
print("Entering context")
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("Exiting context")
async def main():
async with AsyncContextManager():
print("Inside context")
asyncio.run(main())
4. 深入理解asyncio
库的内部机制
asyncio
库是Python中进行异步编程的核心库,它提供了事件循环、协程、任务调度等功能。理解asyncio
的内部机制对于编写高效的异步代码至关重要。
4.1 事件循环的本质
事件循环本质上是一个无限循环,它不断地检查是否有就绪的事件需要处理。这些事件可能包括:
- I/O 事件(例如,socket 连接的建立、数据的接收)
- 定时器事件(例如,
asyncio.sleep()
) - 协程的完成
事件循环使用多路复用技术(例如,select
、poll
、epoll
)来监听多个文件描述符(file descriptor),以便在有事件发生时能够及时地通知。
4.2 任务(Task)的调度
asyncio.create_task()
函数用于创建一个任务。任务是对协程的封装,它可以被事件循环调度执行。当一个任务被创建时,它会被放入事件循环的就绪队列中。事件循环会按照一定的策略(例如,先进先出)从就绪队列中取出任务,并执行它。
4.3 Future 对象
Future
对象代表一个异步操作的最终结果。当一个异步操作开始时,会创建一个 Future
对象。当异步操作完成时,Future
对象会被设置为已完成状态,并包含异步操作的结果或异常。
await
关键字实际上是在等待一个 Future
对象完成。当 await
遇到一个未完成的 Future
对象时,它会将当前协程暂停,并将 Future
对象注册到事件循环中。当 Future
对象完成时,事件循环会恢复当前协程的执行。
4.4 实现简单的事件循环
为了更深入地理解事件循环的内部机制,我们可以手动实现一个简单的事件循环:
import time
import queue
class SimpleEventLoop:
def __init__(self):
self._tasks = queue.Queue()
def create_task(self, coro):
self._tasks.put(coro)
def run_forever(self):
while not self._tasks.empty():
task = self._tasks.get()
try:
next(task)
self._tasks.put(task) # 重新加入队列,等待下次执行
except StopIteration:
pass # 协程执行完毕
async def my_coroutine(n):
print(f"Coroutine {n} started")
await sleep(1) # 使用自定义的 sleep
print(f"Coroutine {n} finished")
async def sleep(delay):
"""
模拟 asyncio.sleep
"""
start_time = time.time()
while time.time() - start_time < delay:
yield # 让出控制权
async def main():
await my_coroutine(1)
await my_coroutine(2)
loop = SimpleEventLoop()
loop.create_task(main().__await__()) # 注意: 需要调用 __await__() 方法
loop.run_forever()
这个简单的事件循环使用一个队列来存储任务。create_task()
函数将协程添加到队列中。run_forever()
函数不断地从队列中取出任务,并执行它。sleep()
函数使用 yield
让出控制权,从而模拟异步操作。
注意: 这个简单的事件循环仅仅是为了演示事件循环的内部机制。在实际开发中,应该使用 asyncio
库提供的事件循环。
5. 生成器和协程的不同
虽然协程是在生成器的基础上构建的,但它们之间存在一些关键的区别:
特性 | 生成器 | 协程 |
---|---|---|
目的 | 产生一系列值,实现迭代器协议 | 实现并发,允许在多个函数之间交替执行 |
关键字 | yield |
async 、await |
调用方式 | next() 或 for 循环 |
await |
返回值 | 产生的值 | Future 对象或其他 awaitable 对象 |
双向通信 | 支持,通过 send() 、throw() 、close() |
支持,通过 await 和 send() (更常见于底层实现) |
上下文切换 | 手动或隐式 | 由事件循环管理 |
协程和生成器:异步编程的基石
我们深入研究了 Python 中的协程和生成器,从基本的生成器函数到高级的 async/await
语法,涵盖了 yield from
的详细机制以及 asyncio
库的核心概念。 理解这些概念对于编写高效、可维护的异步代码至关重要。希望本次分享能够帮助大家更好地掌握 Python 的异步编程技术。