在Python中最佳使用yield的地方?

50 投票
4 回答
42819 浏览
提问于 2025-04-17 04:56

我知道yield是怎么工作的。我也知道排列组合,可以把它看作是数学上的简单概念。

但是yield的真正作用是什么呢?我什么时候应该使用它呢?如果能给个简单好懂的例子就更好了。

4 个回答

5

另一个用法是在网络客户端中。使用“yield”这个关键字在生成器函数里,可以在多个套接字之间轮流处理,而不需要复杂的线程。

举个例子,我有一个硬件测试客户端,需要把一张图片的红、绿、蓝三个颜色通道的数据发送给固件。这些数据需要按照顺序发送:红色、绿色、蓝色,红色、绿色、蓝色。与其创建三个线程,我用一个生成器来从文件中读取数据,并编码成缓冲区。每个缓冲区都是通过“yield buf”来返回的。当文件读取完毕,函数就结束了,这样我就知道迭代结束了。

我的客户端代码循环调用这三个生成器函数,获取缓冲区,直到迭代结束。

23

简单来说,yield 可以让你得到一个生成器。你可以在函数中用它代替通常的 return。下面是一个非常简单的例子,直接从提示中复制过来的……

>>> def get_odd_numbers(i):
...     return range(1, i, 2)
... 
>>> def yield_odd_numbers(i):
...     for x in range(1, i, 2):
...             yield x
... 
>>> foo = get_odd_numbers(10)
>>> bar = yield_odd_numbers(10)
>>> foo
[1, 3, 5, 7, 9]
>>> bar
<generator object yield_odd_numbers at 0x1029c6f50>
>>> next(bar)
1
>>> next(bar)
3
>>> next(bar)
5

从上面的例子可以看到,在第一个情况下,foo 会把整个列表一次性放在内存里。对于一个只有5个元素的列表来说,这没什么大不了的,但如果你想要一个有500万个元素的列表呢?这不仅会占用大量内存,而且在调用函数时也会花费很多时间来构建这个列表。在第二个情况下,bar 只是给你一个生成器。生成器是一种可迭代的对象——这意味着你可以在 for 循环中使用它等等,但每个值只能访问一次。而且所有的值并不是同时存储在内存里的;生成器对象会“记住”上一次你调用它时的循环位置——这样,如果你想用可迭代对象来(比如说)数到500亿,你就不需要一次性数到500亿并把这500亿个数字都存起来。再次强调,这只是一个很简单的例子,如果你真的想数到500亿,可能会用到 itertools。:)

这就是生成器最简单的用法。正如你所说,它可以用来写高效的排列组合,使用 yield 将数据推送到调用栈中,而不是使用某种堆栈变量。生成器还可以用于特殊的树遍历,以及其他各种用途。

进一步阅读:

94

yield 最适合用在你有一个函数需要返回一系列数据,而你又不想一次性把所有数据都放在内存里的时候。

举个例子,我有一个 Python 脚本,它需要处理很多 CSV 文件中的数据,我想把每一行数据传给另一个函数去处理。我不想一次性把几兆的数据都存到内存里,所以我用 yield 每次返回一行数据。获取文件中每一行的函数可能看起来像这样:

def get_lines(files):
    for f in files:
        for line in f:
            #preprocess line
            yield line

然后我可以用和列表一样的方式来访问这个函数的输出:

for line in get_lines(files):
    #process line

这样我就节省了很多内存的使用。

撰写回答