【问题标题】:Yield slower than return in some cases?在某些情况下收益率比回报慢?
【发布时间】:2022-01-12 13:48:44
【问题描述】:

我正在尝试学习收益与回报的用例。在这里,我正在清理字典。但这里的回报似乎更快。只有当我们不需要运行所有迭代 0 到 imax 时,yield 是否会更快?

【问题讨论】:

  • 你为什么假设生成器应该更快?生成器的开销更大。
  • 请将代码发布为文本,而不是文本图像。
  • yieldreturn 有不同的用例——不仅仅是一个比另一个更快或更慢,而是它们做不同的事情。在函数内部,return 语句始终是最后执行的语句,它有效地暂停函数并将控制流返回给调用者。在生成器内部,yield 仅中断流,可以在下次调用该生成器上的 next( ) 时恢复。
  • 您在new_dict[modified_key]==False 行中也有一个错误,我认为您的意思是使用= 而不是==
  • 生成器针对内存的使用进行优化(通过按需生成值而不是一次生成所有值),而不是为了速度。

标签: python function return yield


【解决方案1】:

TLDR:

您看到的时间差异是由于 逐项构建字典与构建列表的性能 元组然后将其转换为字典。不是因为一些 收益与收益的性能差异。

详情:

正如您实施并观察到的两种策略一样,returns 的一种比yeilds 的更快,但这也可能是由于您的策略不同而不是return vs yeild.

您的return 代码逐段构建字典,然后将其返回,而您的yield 策略返回您收集到列表中的元组并将其转换为字典。

如果我们比较将元组列表返回与将元组返回到列表中的时间会发生什么?我们会发现性能基本相同。

首先让我们确定最终会产生相同结果的 3 种方法(您的字典)

首先,让我们构建一些数据进行测试:

import random

## --------------------------
## Some random input data
## --------------------------
feature_dict = {
    f"{'enable' if i%2 else 'disable'}_{i}": random.choice([True, False])
    for i in range(1000)
}
## --------------------------

接下来,我们的三种测试方法。

## --------------------------
## Your "return" strategy
## --------------------------
def reverse_disable_to_enable_return(dic):
    new_dic = {}
    for key, val in dic.items():
        if "enabl" in key:
            new_dic[key] = val
        if "disabl" in key:
            modified_key = key.replace("disable", "enable")
            if val == False:
                new_dic[modified_key] = True
            elif val == True:
                new_dic[modified_key] = False
    return new_dic
## --------------------------

## --------------------------
## Your "yield" strategy (requires cast to dict for compatibility with return)
## --------------------------
def reverse_disable_to_enable_yield(dic):
    for key, val in dic.items():
        if "enabl" in key:
            yield key, val
        if "disabl" in key:
            modified_key = key.replace("disable", "enable")
            if val == False:
                yield modified_key, True
            elif val == True:
                yield modified_key, False
## --------------------------

## --------------------------
## Your "return" strategy modified to return a list to match the yield
## --------------------------
def reverse_disable_to_enable_return_apples(dic):
    new_list = []
    for key, val in dic.items():
        if "enabl" in key:
            new_list.append((key, val))
        if "disabl" in key:
            modified_key = key.replace("disable", "enable")
            if val == False:
                new_list.append((modified_key, True))
            elif val == True:
                new_list.append((modified_key, False))
    return new_list
## --------------------------

现在,让我们从结果的角度验证它们是否基本相同:

## --------------------------
## Do these produce the same result?
## --------------------------
a = reverse_disable_to_enable_return(feature_dict)
b = dict(reverse_disable_to_enable_return_apples(feature_dict))
c = dict(reverse_disable_to_enable_yield(feature_dict))

print(a == feature_dict)
print(a == b)
print(a == c)
## --------------------------

正如我们所希望的,这告诉我们:

False
True
True

现在,时间呢?

让我们建立基本设置上下文:

import timeit

setup = '''
import random
feature_dict = {
    f"{'enable' if i%2 else 'disable'}_{i}": random.choice([True, False])
    for i in range(1000)
}

def reverse_disable_to_enable_return(dic):
    new_dic = {}
    for key, val in dic.items():
        if "enabl" in key:
            new_dic[key] = val
        if "disabl" in key:
            modified_key = key.replace("disable", "enable")
            if val == False:
                new_dic[modified_key] = True
            elif val == True:
                new_dic[modified_key] = False
    return new_dic

def reverse_disable_to_enable_return_apples(dic):
    new_list = []
    for key, val in dic.items():
        if "enabl" in key:
            new_list.append((key, val))
        if "disabl" in key:
            modified_key = key.replace("disable", "enable")
            if val == False:
                new_list.append((modified_key, True))
            elif val == True:
                new_list.append((modified_key, False))
    return new_list

def reverse_disable_to_enable_yield(dic):
    for key, val in dic.items():
        if "enabl" in key:
            yield key, val
        if "disabl" in key:
            modified_key = key.replace("disable", "enable")
            if val == False:
                yield modified_key, True
            elif val == True:
                yield modified_key, False
'''

现在我们准备做一些时间安排......

我们试试吧:

timings_a = timeit.timeit("reverse_disable_to_enable_return(feature_dict)", setup=setup, number=10_000)
print(f"reverse_disable_to_enable_return: {timings_a}")

timings_b = timeit.timeit("dict(reverse_disable_to_enable_yield(feature_dict))", setup=setup, number=10_000)
print(f"reverse_disable_to_enable_yield: {timings_b}")

在我的笔记本电脑上,这是:

reverse_disable_to_enable_return: 2.30
reverse_disable_to_enable_yield: 2.71

确认您观察到yield 显然比return 慢..

但是,请记住,这并不是真正的苹果对苹果的测试。

让我们试试我们的第三种方法

timings_c = timeit.timeit("dict(reverse_disable_to_enable_return_apples(feature_dict))", setup=setup, number=10_000)
print(f"reverse_disable_to_enable_return_apples: {timings_c}")

让我们更接近我们的收益案例:

reverse_disable_to_enable_return_apples: 2.9009995

事实上,让我们将演员阵容带到dict(),看看返回一个元组列表与生成一个元组来构建一个列表......

timings_b = timeit.timeit("list(reverse_disable_to_enable_yield(feature_dict))", setup=setup, number=10_000)
print(f"reverse_disable_to_enable_yield: {timings_b}")

timings_c = timeit.timeit("reverse_disable_to_enable_return_apples(feature_dict)", setup=setup, number=10_000)
print(f"reverse_disable_to_enable_return_apples: {timings_c}")

现在我们得到:

reverse_disable_to_enable_yield: 2.13
reverse_disable_to_enable_return_apples: 2.13

向我们展示了超过 10k 次调用构建和返回元组列表的时间与产生相同元组和构建列表的时间基本相同。正如我们所料。

总结:

您看到的时间差异是由于逐项构建字典与构建元组列表然后将其转换为字典的性能差异。不是因为收益与收益的一些性能差异。

【讨论】:

  • 可靠的答案。你肯定想要那 50+ 的赏金。
【解决方案2】:

我看不出你还需要什么,因为 cmets 已经完美地解释了它,但这里是:

生成器的一个简单示例可能是custom_range(),它将以 1 为增量生成从 0top 的数字。使用它时,它不会一次生成所有数字,所以您不需要立即将它们全部存储起来。一次只能yields 你一个号码。这里的主要用途是 - 无需同时存储 100 000 个变量。

def custom_range(top):

    x = 0

    while x < top:
        yield x
        x += 1


for i in custom_range(100000):
    ...

但如果你要returnlist 的变量,则需要将它们全部存储起来。

def custom_return(top):

    out = []
    x = 0

    while x < top:
        out.append(x)
        x += 1

    return out

my_list = custom_return(100000)

for i in my_list:
    ...

所以我认为直接比较这两件事是不公平的,因为它们都有自己的用例。

【讨论】:

  • 但是,range 实际上并没有作为生成器实现。
  • @Booboo 不是吗?哦,好的,我会编辑答案
猜你喜欢
  • 2017-05-30
  • 1970-01-01
  • 2011-04-20
  • 1970-01-01
  • 2020-02-18
  • 2011-05-27
  • 2015-08-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多