Python 将迭代器转换为列表的最快方法
拥有Python 将迭代器转换为列表的最快方法,python,iterator,list-comprehension,Python,Iterator,List Comprehension,拥有迭代器对象,是否有比列表理解更快、更好或更正确的方法来获取迭代器返回的对象列表 user_list = [user for user in user_iterator] 由于您可以使用*iterable解包运算符: user_list = [*your_iterator] 但要做到这一点: user_list = list(your_iterator) @Robino建议添加一些有意义的测试,因此这里有一个简单的基准测试,用于将迭代器转换为列表的3种可能的方法(可能是最常用的方法):
迭代器
对象,是否有比列表理解更快、更好或更正确的方法来获取迭代器返回的对象列表
user_list = [user for user in user_iterator]
由于您可以使用*
iterable解包运算符:
user_list = [*your_iterator]
但要做到这一点:
user_list = list(your_iterator)
@Robino建议添加一些有意义的测试,因此这里有一个简单的基准测试,用于将迭代器转换为列表的3种可能的方法(可能是最常用的方法):
列表(我的迭代器)
[*我的迭代器]
[e代表我的迭代器中的e]
我一直在使用图书馆
正如您所见,很难区分构造函数转换和解包转换,列表理解转换是“最慢”的方法
我还使用以下简单脚本跨不同的Python版本(3.6、3.7、3.8、3.9)进行了测试:
import argparse
import timeit
parser = argparse.ArgumentParser(
description='Test convert iterator to list')
parser.add_argument(
'--size', help='The number of elements from iterator')
args = parser.parse_args()
size = int(args.size)
repeat_number = 10000
# do not wait too much if the size is too big
if size > 10000:
repeat_number = 100
def test_convert_by_type_constructor():
list(iter(range(size)))
def test_convert_by_list_comprehension():
[e for e in iter(range(size))]
def test_convert_by_unpacking():
[*iter(range(size))]
def get_avg_time_in_ms(func):
avg_time = timeit.timeit(func, number=repeat_number) * 1000 / repeat_number
return round(avg_time, 6)
funcs = [test_convert_by_type_constructor,
test_convert_by_unpacking, test_convert_by_list_comprehension]
print(*map(get_avg_time_in_ms, funcs))
脚本将通过Jupyter笔记本(或脚本)中的子进程执行,大小参数将通过命令行参数传递,脚本结果将取自标准输出
from subprocess import PIPE, run
import pandas
simple_data = {'constructor': [], 'unpacking': [], 'comprehension': [],
'size': [], 'python version': []}
size_test = 100, 1000, 10_000, 100_000, 1_000_000
for version in ['3.6', '3.7', '3.8', '3.9']:
print('test for python', version)
for size in size_test:
command = [f'python{version}', 'perf_test_convert_iterator.py', f'--size={size}']
result = run(command, stdout=PIPE, stderr=PIPE, universal_newlines=True)
constructor, unpacking, comprehension = result.stdout.split()
simple_data['constructor'].append(float(constructor))
simple_data['unpacking'].append(float(unpacking))
simple_data['comprehension'].append(float(comprehension))
simple_data['python version'].append(version)
simple_data['size'].append(size)
df_ = pandas.DataFrame(simple_data)
df_
你可以从我的电脑上得到我完整的笔记本
在大多数情况下,在我的测试中,解包速度更快,但差异非常小,结果可能会随着运行的不同而变化。同样,理解方法是最慢的,事实上,其他两种方法的速度高达约60%。事实上,几乎总是快一点。另外,更明显的是。@systempuntoout它完全用C语言运行。列表理解是用python语言实现的。当然,它运行得更快。我仍然非常讨厌python中没有更好的方法。只需编辑表达式的两侧就可以对其进行切片或索引,这是一件乏味的事情。(在python3中非常常见,如果它是一个纯表达式,比如zip,或者带有纯函数的map)在我的快速测试中,
[*你的迭代器]
似乎是列表(你的迭代器)
的两倍。这通常是真的,还是只是一个特定的事件?(我使用了一个map
作为迭代器。)@Bachsau:无可否认,这很好,但与Bash脚本相比,它可以通过在当前命令的右侧添加一个管道和另一个过滤器命令来操纵当前输出。对于这样一个小的区别(迭代器与物化列表),您经常必须将光标移回。在优化此操作之前,请确保您已经完成了一些分析,以证明这确实是瓶颈。@S.Lott。我通常同意这种态度,但在这种情况下,它应该在风格上进行优化,就像Python经常出现的情况一样,这也会优化它的速度。这是一个非常好的一般性问题,答案很简单,不需要依赖于可以通过探查器运行的特定应用程序。最简洁的方法是[*iterator]
。请发布速度测试的结果以获得更多分数@罗比诺thx的建议,你可以检查
import argparse
import timeit
parser = argparse.ArgumentParser(
description='Test convert iterator to list')
parser.add_argument(
'--size', help='The number of elements from iterator')
args = parser.parse_args()
size = int(args.size)
repeat_number = 10000
# do not wait too much if the size is too big
if size > 10000:
repeat_number = 100
def test_convert_by_type_constructor():
list(iter(range(size)))
def test_convert_by_list_comprehension():
[e for e in iter(range(size))]
def test_convert_by_unpacking():
[*iter(range(size))]
def get_avg_time_in_ms(func):
avg_time = timeit.timeit(func, number=repeat_number) * 1000 / repeat_number
return round(avg_time, 6)
funcs = [test_convert_by_type_constructor,
test_convert_by_unpacking, test_convert_by_list_comprehension]
print(*map(get_avg_time_in_ms, funcs))
from subprocess import PIPE, run
import pandas
simple_data = {'constructor': [], 'unpacking': [], 'comprehension': [],
'size': [], 'python version': []}
size_test = 100, 1000, 10_000, 100_000, 1_000_000
for version in ['3.6', '3.7', '3.8', '3.9']:
print('test for python', version)
for size in size_test:
command = [f'python{version}', 'perf_test_convert_iterator.py', f'--size={size}']
result = run(command, stdout=PIPE, stderr=PIPE, universal_newlines=True)
constructor, unpacking, comprehension = result.stdout.split()
simple_data['constructor'].append(float(constructor))
simple_data['unpacking'].append(float(unpacking))
simple_data['comprehension'].append(float(comprehension))
simple_data['python version'].append(version)
simple_data['size'].append(size)
df_ = pandas.DataFrame(simple_data)
df_