通俗易懂理解python yield

前言

旁边的实习生非常苦恼的问:我每次遇到yield都会困惑,看一次相关博客就感觉好像懂了,但是时间一长又完全记不清了,它不像方法中的return很常用,知道就是用来结束方法,并返回值的。

我:那说明你每次看完博客,认为自己理解了,其实依然处于一知半解,这里需要你有思维上的一个转变。我们接下来,就用通俗易懂的案例帮你一次性彻底理解。

一句话概括

yield 是用来造"生成器"的,它让函数变成一个"懒惰的工厂",只在你需要的时候,才生产一个产品,而不是一口气全做好。

先回忆一下 return

python 复制代码
def get_numbers():
    result = []
    for i in range(5):
        result.append(i)
    return result  # 一次性返回所有结果

nums = get_numbers()
print(nums)  # [0, 1, 2, 3, 4]

这个函数像一个勤劳的厨师:你点单后,他先把5个菜全做完,装在一个盘子里,然后一次性端给你。

但如果要生成 100 万个数字呢?他得先把 100 万个数字全做好,再给你 ------ 这很浪费内存。

现在,yield 上场了

python 复制代码
def get_numbers_yield():
    for i in range(5):
        yield i  # 每次只给你一个,不一口气全做

gen = get_numbers_yield()
print(gen)  # <generator object get_numbers_yield at 0x...>

这时候你拿到的 gen 不是一个列表,而是一个"生成器对象"------ 它像是一个懒惰的厨师,你不说"下一个",他就不做菜。

你什么时候说"下一个"?用 next():

python 复制代码
print(next(gen))  # 0
print(next(gen))  # 1
print(next(gen))  # 2

或者用 for 循环(它自动帮你调用 next):

python 复制代码
for num in get_numbers_yield():
    print(num)  # 依次打印 0, 1, 2, 3, 4

yield 的关键点:暂停和恢复

yield 最神奇的地方是:函数执行到 yield 时会"暂停",记住当前状态,下次调用时从暂停的地方继续。

我们加点打印来看:

python 复制代码
def my_generator():
    print("Step 1")
    yield "A"

    print("Step 2")
    yield "B"

    print("Step 3")
    yield "C"

gen = my_generator()

print("第一次调用:")
print(next(gen))  # 打印 Step 1,然后返回 "A"

print("第二次调用:")
print(next(gen))  # 打印 Step 2,然后返回 "B"

print("第三次调用:")
print(next(gen))  # 打印 Step 3,然后返回 "C"

看到了吗?函数不是一次性跑完的,而是一步步走,走一步停一下,等你叫它才继续。

return vs yield 对比

特性 return yield
返回值 一次性返回所有结果 每次返回一个,可以多次
函数状态 返回后函数结束,状态丢失 暂停,记住状态,下次继续
内存占用 高(全存下来) 低(只生成一个)
返回类型 通常是列表等 生成器对象(可迭代)
使用场景 结果小,需要全部使用 大数据、流式处理、节省内存

举个生活例子:流水线工人

想象你在工厂流水线上:

  • return:工人把1000个零件全组装好,堆成一堆给你。
  • yield:工人每次只组装一个,放到传送带上,你拿一个,他再做下一个。

如果零件很大,堆一堆会占满仓库(内存爆炸)------ 所以 yield 更"聪明"。

通俗易懂的代码示例

🌟 场景 1:处理大数据,节省内存(最常用!)

问题:你想生成 1 亿个数字,但内存不够。

解决:用 yield 按需生成,不占内存。

python 复制代码
# bad:用 return + list ------ 内存爆炸!
def big_list():
    result = []
    for i in range(1000000):  # 100万已经很大了
        result.append(i)
    return result

# good:用 yield ------ 内存几乎为0
def big_generator():
    for i in range(1000000):
        yield i

# 测试
gen = big_generator()

# 只取前5个
for i, num in enumerate(gen):
    if i >= 5:
        break
    print(num)  # 输出 0, 1, 2, 3, 4

✅ 适用:读大文件、数据库大批量数据、日志处理等。

🌟 场景 2:逐行读取大文件(经典案例)

问题:一个 10GB 的日志文件,不能一次性读进内存。

解决:用 yield 一行一行读。

python 复制代码
def read_large_file(filename):
    with open(filename, 'w') as f:  # 先创建测试文件
        for i in range(10):
            f.write(f"日志行 {i}: 用户登录\n")
    
    # 真正的读取函数
    with open(filename, 'r') as f:
        for line in f:
            yield line.strip()

# 测试
for log in read_large_file('test.log'):
    print("处理:", log)

运行后你会看到:

python 复制代码
处理: 日志行 0: 用户登录
处理: 日志行 1: 用户登录

✅ 适用:任何大文件处理。

🌟 场景 3:无限序列生成器

问题:你想生成无限的斐波那契数列、自然数等。

解决:yield 可以"无限产出"。

python 复制代码
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# 只取前10个
fib = fibonacci()
for _ in range(10):
    print(next(fib), end=' ')  # 0 1 1 2 3 5 8 13 21 34

✅ 适用:数学序列、ID 生成、游戏资源生成等。

🌟 场景 4:模拟状态机 / 分步流程

问题:你想实现一个多步骤的流程,比如"登录 → 验证 → 成功"。

解决:用 yield 分步返回。

python 复制代码
def login_flow():
    yield "步骤1: 请输入用户名"
    username = yield "步骤2: 请输入密码"
    if username == "admin":
        yield "步骤3: 登录成功!"
    else:
        yield "步骤3: 用户名错误!"

# 测试
flow = login_flow()
print(next(flow))  # 步骤1
print(next(flow))  # 步骤2
print(next(flow))  # 步骤3(成功或失败)

输出:

python 复制代码
步骤1: 请输入用户名
步骤2: 请输入密码
步骤3: 登录成功!

✅ 适用:向导流程、游戏关卡、审批流程等。

🌟 场景 5:合并多个数据源(管道)

问题:你想从多个地方读数据,像流水线一样处理。

解决:用 yield from 或多个生成器串联。

python 复制代码
def source1():
    yield "A"
    yield "B"

def source2():
    yield "X"
    yield "Y"

def combined():
    yield from source1()  # 把 source1 的所有 yield 拿过来
    yield from source2()

# 测试
for item in combined():
    print(item)  # A, B, X, Y

✅ 适用:数据聚合、ETL(抽取-转换-加载)、日志合并等。

🌟 场景 6:实时数据推送(Web 流)

问题:你想在网页上实时显示时间、股票价格等。

解决:用 yield + StreamingResponse(FastAPI 示例)

python 复制代码
# 安装:pip install fastapi uvicorn
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import time

app = FastAPI()

def generate_time_stream():
    for _ in range(5):
        yield f"data: 当前时间: {time.strftime('%H:%M:%S')}\n\n"
        time.sleep(1)

@app.get("/time")
async def time_stream():
    return StreamingResponse(
        generate_time_stream(),
        media_type="text/event-stream"
    )

# 运行:uvicorn script_name:app --reload
# 然后浏览器访问 http://localhost:8000/time

你会看到时间一秒一秒地更新!

✅ 适用:SSE(Server-Sent Events)、实时监控、聊天室等。

🌟 场景 7:协程 / 生成器通信(高级用法)

问题:你想让生成器接收外部数据。

解决:用 send() 方法。

python 复制代码
def accumulator():
    total = 0
    while True:
        value = yield total  # yield 返回当前 total,并接收新值
        if value is not None:
            total += value

# 测试
acc = accumulator()
print(next(acc))      # 0(启动生成器)
print(acc.send(10))   # 10
print(acc.send(5))    # 15
print(acc.send(-3))   # 12

✅ 适用:状态维护、计算器、游戏得分系统等。

7 种消费生成器的方法

✅ 核心前提:生成器(generator)是一个迭代器(iterator),所以所有能处理"可迭代对象"或"迭代器"的方式,都可以用来消费生成器。

方法 说明 是否推荐 示例
for循环 最常用、最直观 ⭐⭐⭐⭐⭐ for
next()函数 手动控制,一次一个 ⭐⭐⭐⭐☆ next(gen)
内置函数:list()/tuple()/set() 一次性转成容器 ⭐⭐⭐☆☆ list(gen)
解包操作* 展开成参数或列表 ⭐⭐⭐☆☆ [*gen]
传递给需要迭代器的函数 如sum(),max(),any()等 ⭐⭐⭐⭐☆ sum(gen)
作为参数传给函数(如print(*gen)) 结合解包使用 ⭐⭐⭐☆☆ print(*gen)
用于流式响应(如StreamingResponse) Web中的SSE、文件下载等 ⭐⭐⭐⭐⭐ return StreamingResponse(gen)
相关推荐
mortimer3 小时前
Python 进阶:彻底理解类属性、类方法与静态方法
后端·python
小叮当⇔5 小时前
PYcharm——获取天气
ide·python·pycharm
霍志杰5 小时前
记一次csv和xlsx之间的转换处理
python
测试19986 小时前
Jmeter是如何实现接口关联的?
自动化测试·软件测试·python·测试工具·jmeter·职场和发展·接口测试
小蕾Java6 小时前
PyCharm 2025:最新使用图文教程!
ide·python·pycharm
java1234_小锋6 小时前
TensorFlow2 Python深度学习 - 卷积神经网络(CNN)介绍
python·深度学习·tensorflow·tensorflow2
java1234_小锋6 小时前
TensorFlow2 Python深度学习 - 循环神经网络(RNN)- 简介
python·深度学习·tensorflow·tensorflow2
大飞记Python6 小时前
Chromedriver放项目里就行!Selenium 3 和 4 指定路径方法对比 + 兼容写法
开发语言·python
小薛引路6 小时前
office便捷办公06:根据相似度去掉excel中的重复行
windows·python·excel