找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

695

积分

0

好友

97

主题
发表于 17 小时前 | 查看: 1| 回复: 0

Python 函数该用 yield 还是 return?本文将深入探讨这个常见的技术选择问题,帮助你理解两者背后的核心差异及适用场景。

有一天,你写了这样一个函数,目的是计算前 n 个平方数:

def squares_return(n):
    result = []
    for i in range(n):
        result.append(i * i)
    return result

当 n 等于 10 或 100 时,这个函数运行得很好。但如果你心血来潮,将 n 设置为 10_000_000 并运行,电脑的风扇可能就会狂转,内存占用也会急剧上升。

这时,有经验的同事可能会建议你:“试试用 yield 吧。”

问题来了:同样是“返回结果”,为什么有时推荐使用 yield,有时又该老老实实用 return

return:一次性交付所有结果

对于普通函数,你已经很熟悉了:

def add(a, b):
    return a + b

它的特点非常明确:

  1. 函数执行到 return 语句时,立即结束
  2. return 会将一个值(可以是数字、列表、字典等)返回给调用者。
  3. 函数执行完毕后,如果想再次获取结果,必须重新调用它。

像上面的 squares_return 函数,其本质是:

  • 将所有计算结果预先存入一个列表。
  • 函数执行完毕时,一次性 return 这个完整的列表。

当数据量较小时,这种方式简单直接。但一旦数据量变大,内存压力就会骤增。这就像在数据库中执行 SELECT * 语句,数据量小还好,若是几千万行数据,一次性加载无疑是灾难性的。

yield:将函数转变为“结果工厂”

现在,我们换一种写法:

def squares_yield(n):
    for i in range(n):
        yield i * i

这里有两个关键点需要注意:

  1. 只要函数体内出现了 yield 关键字,这个函数就不再是普通函数,而是变成了生成器函数
  2. 调用它时,函数并不会立即执行并返回最终结果,而是返回一个生成器对象
gen = squares_yield(5)
print(gen)  # 输出:<generator object squares_yield at 0x...>

真正的计算发生在你“需要”它的时候:

for x in squares_yield(5):
    print(x)

循环每执行一步,生成器函数就会从上次 yield 暂停的位置继续执行一小段,计算出一个值并通过 yield 送出,然后再次暂停,等待下一次请求。

这就是所谓的惰性计算:能不提前算就不算,什么时候用到,什么时候再计算。

何时使用 yieldreturn 更合适?

一个最典型的场景就是大文件的逐行处理

如果你这样写:

def read_all_lines(path):
    with open(path, 'r', encoding='utf-8') as f:
        return f.readlines()

readlines() 方法会将文件所有行一次性读入内存。当处理一个 200MB 甚至 1GB 的日志文件时,内存可能瞬间被撑满。

改用 yield 后:

def iter_lines(path):
    with open(path, 'r', encoding='utf-8') as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            yield line

使用方式如下:

for line in iter_lines("big.log"):
    # 这里可以进行各种处理,如统计、关键字过滤等
    if "ERROR" in line:
        print(line)

这样做的好处显而易见:

  • 内存中始终只保存当前处理的一行数据(或极少数几行)。
  • 文件再大,程序也能稳定运行,不会导致内存溢出。
  • 逻辑清晰,类似于在数据流中“按需索取”。

在流式算法、日志分析、网络爬虫、数据清洗等场景中,yield 都是不可或缺的利器。

yieldreturn 能在同一个函数中共存吗?

可以,但需要区分两种情况:

  1. 带值的 return
    在生成器函数中:

    def bad_gen():
        yield 1
        return 2

    这里的 return 2 并不会像普通函数那样直接返回值 2,而是会引发一个 StopIteration(2) 异常。在常规业务代码中,这个返回值很少被直接使用,return 的主要作用仅仅是终止生成器

  2. 不带值的 return
    这等价于:

    def gen():
        for i in range(3):
            yield i
        # 执行到这里会自动引发 StopIteration,结束生成

    因此,大多数情况下可以简单地理解为:在包含 yield 的函数中,return 的作用就是“结束,不再产生新的值”。

如果你既想利用生成器的惰性计算优势,又希望最终获得一个完整的结果集,常见的做法是:

def collect_squares(n):
    return list(squares_yield(n))

这样,外部调用者拿到的是一个列表,而内部的算法逻辑仍然可以基于生成器进行灵活的设计和测试。

几个容易混淆的要点

  1. 生成器只能被遍历一次

    gen = squares_yield(3)
    print(list(gen))  # 输出:[0, 1, 4]
    print(list(gen))  # 输出:[]

    生成器耗尽后,再遍历就是空的。如需重复使用,必须重新创建一个新的生成器对象。

  2. 调试时无法直接查看“全部结果”
    在调试过程中,如果你尝试 print(gen),看到的只是一个生成器对象引用,而非计算结果。要查看内容,需要将其转换为列表:

    print(list(squares_yield(5)))
  3. yield 让函数执行流程“看似异步”
    注意,这不是真正的 async/await 异步,而是指代码的执行被分割成一段一段的,由外部迭代来驱动。对于逻辑复杂的情况,新手可能会感到困惑。因此,如果数据量不大且逻辑简单,使用 return 反而更加直观明了。

总结来说,return 是一次性结账,yield 是按次付费。选择哪种方式,取决于你的需求:是希望一次性拿到所有结果,还是愿意在需要时逐个获取以节省资源。理解并正确运用它们,你的代码会更加高效和优雅。

如果您对 Python 或其他编程技术有更多兴趣,欢迎访问 云栈社区 获取更多学习资源和开发者交流机会。




上一篇:海外公务员亲述:从大厂到加拿大边境小镇的考公上岸与转型之路
下一篇:Onlook AI驱动可视化编辑器:开源React应用构建与设计工具
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-26 21:08 , Processed in 0.393258 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表