pin_drop当前位置:知识文库 ❯ 图文

Python yield from完全指南 - 委托生成器与嵌套生成器

一、什么是yield from

yield from是Python 3.3引入的新语法,用于简化生成器之间的委托调用。它允许一个生成器将部分或全部操作委托给另一个生成器、迭代器或可迭代对象。

在没有yield from之前,嵌套生成器需要显式编写循环来转发值,代码冗长且容易出错。yield from让这一切变得优雅简洁。


二、基础语法与工作原理

yield from iterable本质上等价于for item in iterable: yield item,但功能远不止于此。

代码示例

# 基础用法:等价替换
def old_way(iterable):
    """不使用yield from的方式"""
    for item in iterable:
        yield item

def new_way(iterable):
    """使用yield from的方式"""
    yield from iterable

# 两种写法完全等价
print(list(old_way([1, 2, 3])))   # [1, 2, 3]
print(list(new_way([1, 2, 3])))   # [1, 2, 3]
特性 for + yield yield from
基础值传递 支持 支持
send()传递 不支持 支持
throw()传递 不支持 支持
返回值获取 不支持 支持
代码简洁度 冗长 简洁

三、委托生成器与子生成器

yield from的语境中,涉及两个关键角色:

  • 委托生成器(Delegating Generator):包含yield from语句的生成器,负责将调用委托给子生成器

  • 子生成器(Subgenerator):被yield from调用的生成器或可迭代对象,实际产生值的生成器

代码示例

# 子生成器
def sub_generator():
    yield "子生成器: A"
    yield "子生成器: B"
    return "子生成器返回值"

# 委托生成器
def delegating_generator():
    yield "委托: 开始"
    result = yield from sub_generator()  # 委托给子生成器
    yield f"委托: 子生成器返回了 {result}"
    yield "委托: 结束"

# 使用
gen = delegating_generator()
for item in gen:
    print(item)
# 输出:
# 委托: 开始
# 子生成器: A
# 子生成器: B
# 委托: 子生成器返回了 子生成器返回值
# 委托: 结束

四、代码示例与实战

示例1:展平嵌套列表

代码示例

def flatten(nested):
    """递归展平任意深度的嵌套列表"""
    for item in nested:
        if isinstance(item, (list, tuple)):
            yield from flatten(item)  # 递归委托
        else:
            yield item

# 测试
data = [1, [2, [3, 4], 5], [6, 7]]
print(list(flatten(data)))
# [1, 2, 3, 4, 5, 6, 7]

data2 = [["a", "b"], ["c", ["d", "e"]], "f"]
print(list(flatten(data2)))
# ['a', 'b', 'c', 'd', 'e', 'f']

示例2:组合多个生成器

代码示例

def even_numbers():
    for n in range(0, 10, 2):
        yield n

def odd_numbers():
    for n in range(1, 10, 2):
        yield n

def combined():
    yield "偶数:"
    yield from even_numbers()
    yield "奇数:"
    yield from odd_numbers()

print(list(combined()))
# ['偶数:', 0, 2, 4, 6, 8, '奇数:', 1, 3, 5, 7, 9]

示例3:链式管道处理

代码示例

def read_data():
    """模拟读取数据"""
    yield from [1, 2, 3, 4, 5]

def square(data):
    """对每个元素求平方"""
    for item in data:
        yield item ** 2

def filter_positive(data):
    """过滤负数"""
    for item in data:
        if item > 0:
            yield item

def process_pipeline():
    """组合管道:读取 -> 平方 -> 过滤"""
    yield from filter_positive(square(read_data()))

print(list(process_pipeline()))
# [1, 4, 9, 16, 25]

五、注意事项与最佳实践

注意1:yield from后面可以跟任何可迭代对象,不仅仅是生成器。列表、元组、字符串、文件对象等都可以作为yield from的参数。

注意2:子生成器的return语句返回值会被yield from表达式返回。这个特性在协程编程中非常重要,可以用来获取子任务的计算结果。

注意3:yield from会自动处理子生成器抛出的StopIteration异常,并在子生成器结束后恢复委托生成器的执行。不需要手动捕获和处理StopIteration。

小贴士

yield from是Python协程体系的重要基石。在asyncio中,await关键字的设计灵感就来源于yield from。理解yield from有助于深入理解Python异步编程的底层机制。


六、练习题

练习1

使用yield from实现一个生成器函数chain(*iterables),接受任意多个可迭代对象作为参数,依次产出每个可迭代对象的元素。功能类似itertools.chain

练习2

使用yield from实现一个树形结构的遍历生成器。树的节点用{"value": 1, "children": [...]}的字典表示。使用yield from递归遍历所有子节点,返回所有节点的值。

常见问题

yield from和普通的for循环加yield有什么区别?

基础的值传递两者等价。但yield from还能处理send()、throw()和close()等方法的双向通信,并自动传递StopIteration异常,还能获取子生成器的return值。普通for+yield无法实现这些高级功能。

yield from可以用于非生成器的可迭代对象吗?

可以。yield from后面可以跟任何可迭代对象,包括列表、元组、字符串、range对象、文件对象等。它会隐式地将可迭代对象转换为迭代器并逐个产出元素。

yield from和async/await有什么关系?

await的底层设计与yield from非常相似。在Python 3.5引入async/await之前,协程就是通过yield from实现的。await可以看作是针对awaitable对象的yield from,提供了更清晰的语义。

标签: yield from 委托生成器 子生成器 嵌套生成器 Python3.3

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: Python生成器表达式 - 语法、与列表推导式对比及内存优势 下一篇: Python生成器高级应用 - 管道模式、协程与send方法详解

poll相关推荐