`生成器`与`协程`:`yield from`的内部机制与`async/await`的语法糖本质。

生成器与协程:yield from的内部机制与async/await的语法糖本质

大家好,今天我们来深入探讨Python中生成器、协程以及yield fromasync/await之间的关系。我们将从生成器的基本概念入手,逐步揭示yield from的内部工作机制,并最终理解async/await是如何基于生成器和协程实现的。

1. 生成器:迭代器的简化实现

生成器是一种特殊的迭代器。与普通迭代器不同,生成器不需要显式定义__iter____next__方法,而是通过yield关键字来实现迭代过程。

基本概念:

  • 迭代器 (Iterator): 实现了__iter____next__方法的对象,用于逐个访问集合中的元素。
  • 可迭代对象 (Iterable): 实现了__iter__方法的对象,可以返回一个迭代器。
  • 生成器函数 (Generator Function): 包含yield语句的函数,调用时返回一个生成器对象。
  • 生成器表达式 (Generator Expression): 类似于列表推导式,但返回一个生成器对象。

代码示例:

def simple_generator(n):
    for i in range(n):
        yield i

gen = simple_generator(5)
print(type(gen)) # <class 'generator'>

for value in gen:
    print(value) # 0 1 2 3 4

在这个例子中,simple_generator函数就是一个生成器函数。每次执行到yield语句时,函数会暂停执行,并返回yield后面的值。下次调用生成器的__next__方法(或使用for循环迭代)时,函数会从上次暂停的位置继续执行,直到遇到下一个yield或函数结束。

生成器表达式:

gen_exp = (x * x for x in range(5))
print(type(gen_exp)) # <class 'generator'>

for value in gen_exp:
    print(value) # 0 1 4 9 16

生成器表达式提供了一种更简洁的方式来创建生成器。

2. yield的扩展:send()throw()close()

除了基本的yield功能,生成器还提供了send()throw()close()方法,增强了生成器的交互能力。

  • send(value):value发送给生成器函数,并作为当前yield表达式的结果。
  • throw(type, value=None, traceback=None): 在生成器函数中引发一个异常。
  • close(): 关闭生成器,后续的next()调用会引发StopIteration异常。

代码示例:

def echo_generator():
    while True:
        received = yield
        print("Received:", received)

gen = echo_generator()
next(gen) # 启动生成器

gen.send("Hello") # Received: Hello
gen.send("World") # Received: World

gen.close()
# gen.send("Again")  # raises StopIteration

在这个例子中,echo_generator函数可以接收通过send()方法发送的值,并打印出来。 注意,在使用send()之前,必须先调用next()启动生成器,因为生成器函数需要运行到第一个yield语句才能接收值。

3. 协程:并发的另一种实现

协程是一种用户态的轻量级线程,可以在单个线程内实现并发。与线程不同,协程的切换由程序员显式控制,避免了线程切换的开销。

协程与生成器的关系:

在Python中,协程通常基于生成器实现。通过yield语句可以暂停和恢复协程的执行,实现协程之间的切换。send()方法允许向协程传递数据,使其能够响应外部事件。

代码示例:

def coroutine_example():
    print("Coroutine started")
    x = yield
    print("Coroutine received:", x)
    y = yield x * 2
    print("Coroutine received:", y)
    print("Coroutine finished")

co = coroutine_example()
next(co) # 启动协程
print(co.send(10)) # Coroutine received: 10  20
print(co.send(20)) # Coroutine received: 20
try:
    co.send(30) # Coroutine finished
except StopIteration:
    print("Coroutine completed")

这个例子展示了一个简单的协程,通过yield语句暂停和恢复执行,并通过send()方法接收数据。

协程的优势:

  • 轻量级: 协程的创建和切换开销远小于线程。
  • 并发性: 可以在单线程内实现并发,提高程序的效率。
  • 可控性: 协程的切换由程序员显式控制,避免了线程切换的随机性。

4. yield from:连接生成器与协程

yield from 是 Python 3.3 引入的一个语法糖,用于简化生成器和协程的嵌套调用。它可以将一个生成器或可迭代对象的所有值依次产生出来,也可以将控制权委托给另一个协程。

基本用法:

def sub_generator(n):
    for i in range(n):
        yield i

def main_generator(m):
    yield from sub_generator(m)
    yield "Finished"

for value in main_generator(3):
    print(value) # 0 1 2 Finished

在这个例子中,main_generator 使用 yield fromsub_generator 的所有值都产生出来。这相当于将 sub_generator 的代码嵌入到 main_generator 中。

yield from 的内部机制:

yield from 背后涉及一个相对复杂的协议,它主要处理以下几个方面:

  1. 迭代: 从子生成器/迭代器获取值并产生 (yielding)。
  2. send(): 将值发送给子生成器/协程。
  3. throw(): 将异常传递给子生成器/协程。
  4. close(): 关闭子生成器/协程。
  5. 返回值: 获取子生成器/协程的返回值。

可以用伪代码来描述 yield from 的行为:

def yield_from(iterator):
    """
    伪代码,模拟 yield from 的行为
    """
    iterator = iter(iterator)  # 确保是一个迭代器

    try:
        while True:
            try:
                value = next(iterator)
                # 主生成器接收到 send() 的值
                sent = yield value
            except StopIteration as e:
                # 子迭代器完成,获取返回值
                return e.value # Python 3.3+ 可以获取 StopIteration 的 value
            except GeneratorExit:
                # 主生成器被 close()
                try:
                    iterator.close()
                except AttributeError:
                    pass  # 子迭代器可能没有 close() 方法
                raise
            except BaseException as e:
                # 主生成器接收到 throw() 的异常
                try:
                    iterator.throw(e)
                except StopIteration as e2:
                    return e2.value # 子迭代器处理异常后完成
                except BaseException as e2:
                    raise e2 # 子迭代器无法处理异常,向上抛出

            else:
                # 主生成器通过 send() 发送了值
                try:
                    iterator.send(sent)
                except StopIteration as e:
                    return e.value # 子迭代器处理 send() 后完成
    finally:
        # 确保关闭子迭代器
        try:
            iterator.close()
        except AttributeError:
            pass

这个伪代码展示了 yield from 如何处理迭代、send()throw()close() 等操作,以及如何获取子生成器的返回值。

yield from 与协程:

在协程中,yield from 可以将控制权委托给另一个协程,实现协程之间的协作。

def sub_coroutine():
    x = yield
    print("Sub coroutine received:", x)
    return x * 2

def main_coroutine():
    result = yield from sub_coroutine()
    print("Main coroutine received result:", result)

co = main_coroutine()
next(co) # 启动主协程
print(co.send(10)) # Sub coroutine received: 10
# Main coroutine received result: 20
# raises StopIteration

在这个例子中,main_coroutine 使用 yield from 将控制权委托给 sub_coroutinesub_coroutine 接收到 send() 方法发送的值,并返回一个结果。main_coroutine 接收到 sub_coroutine 的结果,并打印出来。

5. async/await:协程的语法糖

async/await 是 Python 3.5 引入的语法糖,用于更简洁地定义和使用协程。它基于生成器和 yield from 实现,提供了更清晰的异步编程模型。

基本概念:

  • async: 用于声明一个协程函数。
  • await: 用于等待一个协程完成并获取其结果。

代码示例:

import asyncio

async def my_coroutine(n):
    print("Coroutine started:", n)
    await asyncio.sleep(1)  # 模拟异步操作
    print("Coroutine finished:", n)
    return n * 2

async def main():
    task1 = asyncio.create_task(my_coroutine(1))
    task2 = asyncio.create_task(my_coroutine(2))

    result1 = await task1
    result2 = await task2

    print("Result 1:", result1)
    print("Result 2:", result2)

asyncio.run(main())

在这个例子中,my_coroutine 函数使用 async 声明为协程函数。await asyncio.sleep(1) 用于等待一个异步操作完成。main 函数也使用 async 声明为协程函数,它创建了两个 my_coroutine 协程的任务,并使用 await 等待它们完成。

async/await 的本质:

async/await 实际上是基于生成器和 yield from 的语法糖。async 声明的函数会被转换成一个生成器函数,await 表达式会被转换成 yield from 表达式。

可以用伪代码来描述 async/await 的转换过程:

# 原始代码
async def my_coroutine(n):
    await asyncio.sleep(1)
    return n * 2

# 转换后的代码
def my_coroutine(n):
    gen = asyncio.sleep(1).__await__()  # 获取 awaitable 对象的迭代器
    yield from gen
    return n * 2

实际上asyncio库的处理更加复杂,但从概念上讲,await会将控制权交还给事件循环,直到asyncio.sleep(1)完成。

async/await 的优势:

  • 简洁性: async/await 提供了更简洁的语法来定义和使用协程。
  • 可读性: async/await 使异步代码更易于阅读和理解。
  • 易用性: async/await 降低了异步编程的门槛。

asyncio 库:

asyncio 是 Python 的标准库,提供了异步编程的基础设施,包括事件循环、协程调度、异步 I/O 等。

asyncio.create_task(): 用于创建一个任务,将协程放入事件循环中执行。
asyncio.run(): 用于运行一个主协程,启动事件循环。

6. 总结:生成器与协程的演进

我们可以用一个表格来总结生成器、协程、yield fromasync/await 之间的关系:

特性 生成器 (Generator) 协程 (Coroutine) yield from async/await
基本概念 迭代器的简化实现 用户态轻量级线程 语法糖 语法糖
实现方式 yield 关键字 基于生成器 基于 yield 基于生成器
主要用途 迭代数据 并发编程 连接生成器/协程 简化协程编程
增强特性 send(), throw(), close() 异步 I/O 委托控制权 更清晰的语法
Python版本 2.5 2.5+ 3.3 3.5

生成器是协程的基础,协程通过 yield 语句实现暂停和恢复,yield from 用于连接生成器和协程,而 async/await 则是协程的语法糖,提供了更简洁的异步编程模型。

7. 掌握生成器与协程,构建更高效的程序

理解生成器、协程、yield fromasync/await 的内部机制,可以帮助我们更好地利用 Python 的异步编程能力,构建更高效、更可维护的程序。从迭代器的简化,到并发编程的利器,再到语法糖的加持,掌握这些知识点,你就能写出更Pythonic的代码。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注