Python技术迷

Python 函数返回yield还是return?这是个问题。

Python 函数该用 yield 还是 return?我来跟你唠唠这个坑

有一天你写了这么个函数,想把前 n 个平方都算出来:

defsquares_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:一次性把结果全塞给你

普通函数你已经很熟了:

defadd(a, b):
return a + b

特点很简单:

  1. 函数从头跑到 return,直接结束。
  2. return 把一个值(或者元组、字典、列表之类)丢给调用者。
  3. 再想让它干活,只能重新调用一次。

像刚才的 squares_return,本质上就是:

  • 把所有结果先放到一个列表
  • 函数执行完,一次性 return 这个大列表

数据少的时候很舒服,数据一大,内存压力就上来了。就像查数据库时一次性 SELECT * 全捞出来,少量还行,几千万行就有点作死了

yield:函数变成“结果工厂”

换个写法:

defsquares_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 出去,然后又停住,等你下一次再要。

这就是所谓的惰性计算: 能拖就拖,什么时候用,什么时候算。

什么时候 yield 比 return 香很多?

最典型的一个:大文件按行处理。

如果你这样写:

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

readlines() 会直接把所有行读进内存。日志文件 200MB、1GB 的时候,内存直接被你干翻。

换成 yield:

defiter_lines(path):
with open(path, 'r', encoding='utf-8') as f:
for line in f:
            line = line.strip()
ifnot line:
continue
yield line

用的时候:

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

好处就是:

  • 内存里始终只会有一行(或者说极少数几行)
  • 文件再大,也能稳稳地跑完
  • 逻辑上也很清晰,就像在流里“刷数据”

各种流式算法、日志分析、爬虫、数据清洗里,yield 都是常客。

yield 和 return 能一起用吗?

能用,但要分清两种情况:

  1. 带值的 return

    在生成器函数里:

    defbad_gen():
    yield1
    return2

    这个 return 2 不会像普通函数那样“返回 2”,而是抛出一个 StopIteration(2),正常写业务基本用不到这个返回值,一般就是结束生成器而已。

  2. 不带值的 return

    这个就相当于:

    defgen():
    for i in range(3):
    yield i
    # 走到这儿自动 StopIteration

    所以大部分时候,你可以简单理解为:在用 yield 的函数里,return 就是“结束,不再产出了”。

如果你想要一个“完整结果”,又想中间用到 yield,很常见的做法是:

defcollect_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高级架构师资料合集》,总量高达650GB