使用生成器函数构建列表是否有效

2024-07-02 12:40:52 发布

您现在位置:Python中文网/ 问答频道 /正文

在阅读brettslatkin的《有效的Python》一书时,我注意到作者建议,有时使用生成器函数和对生成的迭代器调用list来构建一个列表,可以产生更干净、更可读的代码。你知道吗

举个例子:

num_list = range(100)

def num_squared_iterator(nums):
    for i in nums:
        yield i**2

def get_num_squared_list(nums):
    l = []
    for i in nums:
        l.append(i**2)
    return l

用户可以调用

l = list(num_squared_iterator(num_list))

或者

l = get_num_squared_list(nums)

得到同样的结果。你知道吗

建议generator函数的噪声更小,因为它更短,并且没有创建列表和向列表中附加值的额外代码。你知道吗

(请清楚地注意,对于这些简单的示例,列表理解或生成器表达式会更好,但让我们假设这是一种模式的简化,可以用于列表理解中不清楚的更复杂代码)

我的问题是,在列表中包装生成器是否需要成本?它的性能是否等同于列表构建功能?你知道吗


Tags: 函数代码in列表forgetdef作者
2条回答

我可以确认带有list示例的生成器更快:

In [4]: def num_squared_iterator(nums):
   ...:     for i in nums:
   ...:         yield i**2
   ...:
   ...: def get_num_squared_list(nums):
   ...:     l = []
   ...:     for i in nums:
   ...:         l.append(i**2)
   ...:     return l
   ...:

In [5]: %timeit list(num_squared_iterator(nums))
320 µs ± 4.57 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [6]: %timeit get_num_squared_list(nums)
370 µs ± 25.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [7]: nums = range(100000)

In [8]: %timeit list(num_squared_iterator(nums))
33.2 ms ± 461 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [9]: %timeit get_num_squared_list(nums)
36.3 ms ± 375 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

然而,还有更多的故事。传统观点认为,生成器比迭代其他类型的iterables要慢,因此生成器的开销很大。但是,使用list会将列表构建代码向下推到C级,所以您看到了一种中间立场。注意,使用for循环可以优化如下:

In [10]: def get_num_squared_list_microoptimized(nums):
    ...:     l = []
    ...:     append = l.append
    ...:     for i in nums:
    ...:         append(i**2)
    ...:     return l
    ...:

In [11]: %timeit list(num_squared_iterator(nums))
33.4 ms ± 427 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [12]: %timeit get_num_squared_list(nums)
36.5 ms ± 624 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [13]: %timeit get_num_squared_list_microoptimized(nums)
33.3 ms ± 487 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

现在您看到,如果您“内联”l.append(这是list构造函数所避免的),方法中的许多差异都可以得到改善。一般来说,Python中的方法解析速度很慢。在紧密循环中,上面的微优化是众所周知的,并且是使for循环更高效的第一步。你知道吗

看到这一点,我决定做一个快速测试,并编写和运行以下代码:

from functools import wraps
from time import time

TEST_DATA = range(100)


def timeit(func):
    @wraps(func)
    def wrapped(*args, **kwargs):
        start = time()
        func(*args, **kwargs)
        end = time()
        print(f'running time for {func.__name__}  = {end-start}')
    return wrapped

def num_squared_iterator(nums):
    for i in nums:
        yield i**2

@timeit
def get_num_squared_list(nums):
    l = []
    for i in nums:
        l.append(i**2)
    return l

@timeit
def get_num_squared_list_from_iterator(nums):
    return list(num_squared_iterator(nums))


if __name__ == '__main__':
    get_num_squared_list(TEST_DATA)
    get_num_squared_list_from_iterator(TEST_DATA)

我运行了很多次测试代码,每次(令我惊讶的是)get\u num\u squared\u list\u from\u iterator函数实际上比get\u num\u squared\u list函数运行得(稍微)快。你知道吗

以下是我头几次跑步的结果:

1。 get_num_squared_list的运行时间=5.2928924560546875e-05

从迭代器获取平方列表的运行时间=5.0067901611328125e-05

2。 get\u num\u squared\u list的运行时间=5.3882598876953125e-05

从迭代器获取平方列表的运行时间=4.982948303222656e-05

三。 get\u num\u squared\u list的运行时间=5.1975250244140625e-05

从迭代器获取平方列表的运行时间=4.76837158203125e-05

我猜这是因为做一个列表.append在循环的每次迭代中使用get\u num\u squared\u list函数。你知道吗

我觉得这很有趣,因为代码不仅清晰优雅,而且性能更高。你知道吗

相关问题 更多 >