当前位置: 首页
编程语言
Python生成器进阶 从yield到yield from实现省内存生产者消费者模式

Python生成器进阶 从yield到yield from实现省内存生产者消费者模式

时间:2026-08-03
转载

Python生成器不仅实现惰性求值,还通过send()、throw()、close()支持双向通信,yieldfrom实现生成器委托与协程组合。这些机制可用于构建省内存的生产者-消费者模式和流水线,逐条处理大数据流,显著降低内存占用。

Python 的生成器(Generator)是这门语言里最被低估的高级特性之一。许多开发者仅仅把它当作一个“懒加载”的迭代器来使用,但实际上,它内置了一整套双向通信和协程组合的能力。一旦掌握了 send()、throw()、close() 以及 yield from,在处理大数据流、实现协程、构建流水线模式时,代码不仅能写得优雅简洁,还能把内存占用压到极致。下面我们一步步拆解这些核心机制。

一、知识点简介

生成器是 Python 最被低估的高级特性之一。很多人只把它当作“惰性迭代器”来用,实际上 send()/throw()/close() 让生成器拥有了双向通信能力,yield from 则提供了生成器委托/协程组合的能力。掌握这些,你可以在处理大数据流、实现协程、构建管道模式时写出极其优雅且节省内存的代码。

Python 开发技巧 · 生成器(Generator)进阶 —— 从 yield 到 yield from,写出省内存的生产者-消费者模式

二、核心概念速览

机制 作用
yield x 产出 x,暂停执行,保留当前状态
gen.send(val) 从外部向生成器内部发送值,val 会成为 yield 表达式的返回值
gen.throw(exc) 在生成器暂停处抛出指定异常
gen.close() 在暂停处抛出 GeneratorExit,终止生成器运行
yield from sub_gen 委托给子生成器,自动传递 send/throw/close 消息

三、核心代码 & 使用示例

1. 基础:省内存的大文件逐行处理

def read_large_file(filepath: str):
    """逐行读取,任何时候只保留一行在内存"""
    with open(filepath, encoding="utf-8") as f:
        for line in f:
            yield line.strip()

# 使用(100MB 文件也毫无压力)
for line in read_large_file("big_log.txt"):
    if "ERROR" in line:
        print(line)

2. 进阶:用 send() 实现双向通信 —— 简易累加器

def running_a verage():
    """动态累加器:外部边送数据边拿当前均值"""
    total, count = 0.0, 0
    a verage = None
    while True:
        value = yield a verage          # yield 产出上次均值,暂停等待 send
        total += value
        count += 1
        a verage = total / count

a vg = running_a verage()
next(a vg)                             # 启动生成器(走到第一个 yield)
print(a vg.send(10))   # 10.0
print(a vg.send(20))   # 15.0
print(a vg.send(30))   # 20.0
print(a vg.send(40))   # 25.0

3. 实战:生产者-消费者协程模式

def consumer():
    """消费者:不断接收数据并处理"""
    items = []
    while True:
        item = yield
        if item is None:          # 哨兵值,终止
            break
        items.append(item)
        print(f"[消费] {item}")
    return items

def producer(data, consumer_gen):
    """生产者:发送数据给消费者"""
    next(consumer_gen)            # 启动
    for item in data:
        consumer_gen.send(item)
    consumer_gen.send(None)       # 发送终止信号

# 运行
c = consumer()
producer(["A", "B", "C", "D"], c)
# 输出:
# [消费] A
# [消费] B
# [消费] C
# [消费] D

4. yield from —— 生成器委托,优雅组合

def sub_gen_a():
    yield "A1"
    yield "A2"

def sub_gen_b():
    yield "B1"
    yield "B2"

def main_gen():
    yield "START"
    yield from sub_gen_a()      # 委托:自动转发 send/throw/close
    yield "MIDDLE"
    yield from sub_gen_b()
    yield "END"

print(list(main_gen()))
# ['START', 'A1', 'A2', 'MIDDLE', 'B1', 'B2', 'END']

yield from 的真正威力:它会自动将 send() / throw() / close() 转发到子生成器,让外部代码可以直接与最内层生成器通信,无需手动传递。

5. 实战:流水线(Pipeline)模式处理数据流

def lines(filepath):
    with open(filepath) as f:
        yield from f

def filter_lines(lines_gen, keyword):
    for line in lines_gen:
        if keyword in line:
            yield line

def parse_json_lines(lines_gen):
    import json
    for line in lines_gen:
        yield json.loads(line)

def extract_field(json_gen, field):
    for obj in json_gen:
        yield obj.get(field)

# 组装管道(零拷贝,全程惰性求值)
pipe = extract_field(
    parse_json_lines(
        filter_lines(
            lines("data.jsonl"),
            "ERROR"
        )
    ),
    "timestamp"
)

for ts in pipe:
    print(f"错误时间: {ts}")

每条管道节点都是一个生成器,数据逐条流过,内存占用 = 一条记录的大小,与文件总大小无关。

6. 生成器表达式 vs 列表推导 —— 内存对比

import sys

list_comp = [x ** 2 for x in range(10_000_000)]   # 立即构建全部
gen_expr  = (x ** 2 for x in range(10_000_000))    # 惰性,不占内存

print(f"列表推导: {sys.getsizeof(list_comp) / 1024 / 1024:.1f} MB")  # ~305 MB
print(f"生成器:   {sys.getsizeof(gen_expr)} bytes")                   # ~112 字节

四、注意事项 / 避坑指南

  1. 生成器只能遍历一次 —— 第二次遍历将返回空结果。如果需要复用,可以用 list() 转换为列表,或使用 itertools.tee() 复制。
  2. 必须 next() 启动 —— 对于带 send() 的生成器,第一次 send() 必须传 None,否则会抛出 TypeError。
  3. 异常处理 —— 生成器内部抛出的异常会向上冒泡到调用方,可以使用 gen.throw() 来测试生成器的异常处理能力。
  4. 关闭生成器 —— 不再使用时可以调用 gen.close() 释放资源,生成器内部可以用 try/finally 做清理操作。
  5. 不要混用 return 和 yield —— 从 Python 3.3+ 开始,生成器 return 的值会附加到 StopIteration.value 中,需通过 yield from 捕获。
def gen_with_return():
    yield 1
    yield 2
    return "完成"

g = gen_with_return()
print(list(g))  # [1, 2] —— return 值被忽略!

# 正确获取 return 值的方式
def wrapper():
    result = yield from gen_with_return()
    print(f"子生成器返回: {result}")

list(wrapper())  # 输出: 子生成器返回: 完成

五、总结

生成器远不止是“省内存的迭代器”:

  • send() —— 让外部和生成器双向对话,实现协程模式
  • yield from —— 优雅组合生成器,自动转发消息
  • 流水线模式 —— 链式处理大数据流,每条记录在管道中逐级流过

下次面对大量数据处理或需要生产者-消费者模式的场景,用生成器代替先组装列表再处理的写法,不仅代码更优雅,内存性能也会显著提升。

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
35岁转行网络安全:从经验复用到实战落地的可行性评估

35岁转行网络安全:从经验复用到实战落地的可行性评估

35岁转行网络安全并非不可行,但核心在于将过往经验转化为安全领域的差异化优势。本文从岗位匹配度、技能学习顺序、实战验证闭环、求职策略及常见误区五个维度,提供一套可执行的转行评估框架与行动指南,帮助读者理性判断投入产出比,避开无效学习陷阱。

时间:2026-10-10 18:07
网络安全行业前景分析:技术演进与市场机遇

网络安全行业前景分析:技术演进与市场机遇

围绕2026年网络安全行业的发展变化,从市场需求、技术演进、细分赛道和企业落地四个层面展开,帮助读者理解行业增长逻辑、识别重点技术方向,并建立评估市场机遇与风险的基本框架。 OWASP China +2 IDC +2

时间:2026-10-10 18:02
2026网络安全求职全景:从岗位拆解到实战作品集构建

2026网络安全求职全景:从岗位拆解到实战作品集构建

本文基于2026年网络安全行业招聘趋势,深入剖析安全运维、攻防渗透、云安全等核心岗位的技术栈差异与能力侧重。文章不仅梳理了从基础网络知识到高级攻防演练的学习路径,更提供了“以终为始”的求职策略:通过拆解JD反向验证技能缺口,并指导如何将CTF经历、HomeLab实验转化为具有说服力的项目作品集,帮助

时间:2026-10-10 17:57
2024安全攻防实战:从勒索软件到AI治理的破局与重构

2024安全攻防实战:从勒索软件到AI治理的破局与重构

2024年的网络安全已从单纯的技术对抗演变为业务连续性的生死博弈。本文基于ENISA、微软及世界经济论坛的最新报告,深入剖析勒索软件的“双重勒索”演变、身份凭证成为首要攻击面的现状,以及生成式AI带来的攻防不对称性。文章进一步拆解企业如何从被动防御转向“发现-保护-检测-响应-恢复”的闭环体系,重点

时间:2026-10-10 17:52
网站编程AI工具测评:提升开发效率的辅助软件推荐

网站编程AI工具测评:提升开发效率的辅助软件推荐

围绕网站开发中的实际需求,对AI编程辅助工具进行分类、操作体验与效果验证,帮助读者快速判断哪些工具真正能提升开发效率,并避开代码质量、隐私、安全与过度依赖等常见问题。

时间:2026-10-10 17:47
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全