在某些情况下产量比 return 慢?

Yield slower than return in some cases?

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

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

生成器的一个简单示例可以是 custom_range(),它将生成从 0top 的数字,增量为 1。当您使用它时,它不会一次生成所有数字,因此您不需要立即将它们全部存储。它一次只会 yield 给你一个数字。这里的主要用途是——不需要同时存储这100000个变量。

def custom_range(top):

    x = 0

    while x < top:
        yield x
        x += 1


for i in custom_range(100000):
    ...

但如果您要 return 变量的 list,则需要将它们全部存储。

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:
    ...

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

TLDR:

The differences in timings you see is due to the difference in performance of building a dictionary item by item vs building a list of tuples then casting that to a dictionary. NOT as a result of some performance difference with return vs yield.

详情:

正如您实施和观察到的两种策略一样,returnsyeilds 更快,但这也可能是由于您的策略不同而导致的比 returnyeild.

您的 return 代码逐段构建字典,然后 return 在您的 yield 策略 return 收集到列表并投射的元组时对其进行

到字典。

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

首先让我们确定最终会产生相同结果的 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}")

让我们更接近我们的 yield 案例:

reverse_disable_to_enable_return_apples: 2.9009995

事实上,让我们将转换为 dict() 并查看 return 生成元组列表与生成元组以构建列表...

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 调用构建和 return 元组列表的时间与生成相同元组和构建列表的时间基本相同。正如我们所料。

总结:

您看到的时间差异是由于逐项构建字典与构建元组列表然后将其转换为字典的性能差异所致。不是由于 return 与 yield.

的一些性能差异