【发布时间】:2023-03-25 10:22:01
【问题描述】:
我知道yield 的工作原理。我知道排列,认为它只是一种数学简单性。
但是yield 的真正力量是什么?我应该什么时候使用它?一个简单而好的例子更好。
【问题讨论】:
我知道yield 的工作原理。我知道排列,认为它只是一种数学简单性。
但是yield 的真正力量是什么?我应该什么时候使用它?一个简单而好的例子更好。
【问题讨论】:
yield 最适合在您有一个返回序列的函数并且想要迭代该序列但您不需要一次将每个值都保存在内存中时使用。
例如,我有一个解析大量 CSV 文件的 python 脚本,我想返回每一行以在另一个函数中处理。我不想一次将数兆字节的数据存储在内存中,所以我yield 每一行都在一个python 数据结构中。因此,从文件中获取行的函数可能类似于:
def get_lines(files):
for f in files:
for line in f:
#preprocess line
yield line
然后我可以使用与列表相同的语法来访问此函数的输出:
for line in get_lines(files):
#process line
但我节省了很多内存使用量。
【讨论】:
yield 在哪里出现?
get_lines() 可以是一些任意函数,它返回具有已知结构的对象序列,而yield 允许它返回大量此类对象而不会使用太多记忆。
简单地说,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 通过调用堆栈向上推送,而不是使用某种堆栈变量。生成器也可以用于专门的树遍历,以及其他各种方式。
进一步阅读:
【讨论】:
另一个用途是在网络客户端中。在生成器函数中使用 'yield' 来循环通过多个套接字,而不需要线程的复杂性。
例如,我有一个硬件测试客户端,需要将图像的 R、G、B 平面发送到固件。需要同步发送的数据:红色、绿色、蓝色、红色、绿色、蓝色。我没有产生三个线程,而是有一个从文件中读取的生成器,对缓冲区进行编码。每个缓冲区都是一个'yield buf'。文件结束,函数返回,我已经结束迭代。
我的客户端代码循环通过三个生成器函数,获取缓冲区直到迭代结束。
【讨论】:
我正在阅读 Python 中的数据结构和算法
有一个使用yield的斐波那契函数。我认为现在是使用 yield 的最佳时机。
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a+b
你可以这样使用:
gen = fibonacci()
for i, f in enumerate(gen):
print(i, f)
if i >= 100: break
所以,我认为,也许,当下一个元素依赖于之前的元素时,例如数字滤波器,是时候使用 yield。
【讨论】: