【问题标题】:Where to use yield in Python best?在 Python 中哪里最好使用 yield?
【发布时间】:2023-03-25 10:22:01
【问题描述】:

我知道yield 的工作原理。我知道排列,认为它只是一种数学简单性。

但是yield 的真正力量是什么?我应该什么时候使用它?一个简单而好的例子更好。

【问题讨论】:

标签: python yield


【解决方案1】:

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 在哪里出现?
  • 谢谢,我有点困惑。 for line in f.readlines(): #process line 做同样的事情。好像不需要屈服,还是readall()中的屈服?
  • 我添加了实际的函数定义来澄清这一点
  • so get_lines() 封装了文件的工作,但用户也可以使用迭代来调用 readlines(),效果相同,我认为不需要屈服。
  • 这个想法是get_lines() 可以是一些任意函数,它返回具有已知结构的对象序列,而yield 允许它返回大量此类对象而不会使用太多记忆。
【解决方案2】:

简单地说,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 通过调用堆栈向上推送,而不是使用某种堆栈变量。生成器也可以用于专门的树遍历,以及其他各种方式。

进一步阅读:

【讨论】:

  • 第二个例子也同时将整个列表保存在内存中,因为它需要保留整个列表来支持生成器。
【解决方案3】:

另一个用途是在网络客户端中。在生成器函数中使用 'yield' 来循环通过多个套接字,而不需要线程的复杂性。

例如,我有一个硬件测试客户端,需要将图像的 R、G、B 平面发送到固件。需要同步发送的数据:红色、绿色、蓝色、红色、绿色、蓝色。我没有产生三个线程,而是有一个从文件中读取的生成器,对缓冲区进行编码。每个缓冲区都是一个'yield buf'。文件结束,函数返回,我已经结束迭代。

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

【讨论】:

  • 谢谢。是的,'3 线程 + 锁'不好。但为什么在同一个主线程中?
  • 简单。该脚本是一个小型命令行应用程序。没有图形用户界面。此外,同一线程中的所有内容都意味着一个套接字上的错误将关闭整个客户端。由于我只与一台服务器通信,一个套接字的死亡意味着我可以快速停止所有套接字。
【解决方案4】:

我正在阅读 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。

【讨论】:

    猜你喜欢
    • 2016-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-22
    • 2015-09-19
    • 2022-08-18
    • 2016-04-28
    相关资源
    最近更新 更多