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
特点很简单:
return,直接结束。return 把一个值(或者元组、字典、列表之类)丢给调用者。像刚才的 squares_return,本质上就是:
return 这个大列表数据少的时候很舒服,数据一大,内存压力就上来了。就像查数据库时一次性 SELECT * 全捞出来,少量还行,几千万行就有点作死了
yield:函数变成“结果工厂”换个写法:
defsquares_yield(n):
for i in range(n):
yield i * i
注意两件事:
yield,这个函数就不再是普通函数,而是生成器函数。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 能一起用吗?能用,但要分清两种情况:
带值的 return
在生成器函数里:
defbad_gen():
yield1
return2
这个 return 2 不会像普通函数那样“返回 2”,而是抛出一个 StopIteration(2),正常写业务基本用不到这个返回值,一般就是结束生成器而已。
不带值的 return
这个就相当于:
defgen():
for i in range(3):
yield i
# 走到这儿自动 StopIteration
所以大部分时候,你可以简单理解为:在用 yield 的函数里,return 就是“结束,不再产出了”。
如果你想要一个“完整结果”,又想中间用到 yield,很常见的做法是:
defcollect_squares(n):
return list(squares_yield(n))
外面用的时候还是拿到一个列表,只不过内部逻辑你可以自由地先用生成器把算法拆开、测完,再决定要不要一次性收集起来。
说几个经常把人搞懵的点:
生成器只能遍历一遍
gen = squares_yield(3)
print(list(gen)) # [0, 1, 4]
print(list(gen)) # []
想重复用,就重新创建一个新的生成器。
调试的时候看不到“全部结果”
写着写着有时候想 print(gen) 看看里面到底有什么,其实看不到,它就是个“还没算”的东西。想看结果还是得:
print(list(squares_yield(5)))
yield 让函数变“异步”一样
不是那种真正的 async/await 异步啊,而是说代码不是从上到下一口气跑完,而是一截一截地被外界驱动,有时候逻辑一复杂,新手就会晕。所以如果数据量不大,逻辑简单,用 return 反而更直观。
说白了,**return 是一次性结账,yield 是按次付费**。 你要的是一口气买下来的那套,还是每次用一点再付一点钱,这个选对了,代码自然就顺了。
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB