在阅读Brett Slatkin的书"Effective 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
Run Code Online (Sandbox Code Playgroud)
用户可以打电话的地方
l = list(num_squared_iterator(num_list))
Run Code Online (Sandbox Code Playgroud)
要么
l = get_num_squared_list(nums)
Run Code Online (Sandbox Code Playgroud)
并得到相同的结果.
建议生成器函数具有较少的噪声,因为它较短并且没有用于创建列表和向其附加值的额外代码.
(请注意,对于这些简单的示例,列表理解或生成器表达式会更好,但让我们假设这是一个模式的简化,可用于更复杂的代码,这在列表理解中是不明确的)
我的问题是,将生成器包装在列表中是否有成本?它在性能上与列表构建功能相同吗?
看到这一点,我决定做一个快速测试并编写并运行以下代码:
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)
Run Code Online (Sandbox Code Playgroud)
我多次运行测试代码,每次(令我惊讶的是) get_num_squared_list_from_iterator 函数实际上比 get_num_squared_list 函数运行(小部分)快。
以下是我前几次运行的结果:
1. get_num_squared_list = 5.2928924560546875e-05 的运行时间
get_num_squared_list_from_iterator 的运行时间 = 5.0067901611328125e-05
2. get_num_squared_list = 5.3882598876953125e-05 的运行时间
get_num_squared_list_from_iterator = 4.982948303222656e-05 的运行时间
3. get_num_squared_list = 5.1975250244140625e-05 的运行时间
get_num_squared_list_from_iterator 的运行时间 = 4.76837158203125e-05
我猜测这是由于在 get_num_squared_list 函数的循环的每次迭代中执行 list.append 的费用所致。
我觉得这很有趣,因为代码不仅清晰、优雅,而且看起来性能更高。