有了迭代器对象,是否有比列表推导式更快、更好或更正确的方法来获得迭代器返回的对象列表?
user_list = [user for user in user_iterator]
有了迭代器对象,是否有比列表推导式更快、更好或更正确的方法来获得迭代器返回的对象列表?
user_list = [user for user in user_iterator]
当前回答
@Robino建议添加一些有意义的测试,所以这里有一个简单的基准测试,有3种可能的方法(可能是最常用的)来将迭代器转换为列表:
按类型构造函数 列表(my_iterator) 通过拆包 (* my_iterator) 使用列表推导式 [e for my_iterator中的e]
我一直在使用simple_bechmark库:
from simple_benchmark import BenchmarkBuilder
from heapq import nsmallest
b = BenchmarkBuilder()
@b.add_function()
def convert_by_type_constructor(size):
list(iter(range(size)))
@b.add_function()
def convert_by_list_comprehension(size):
[e for e in iter(range(size))]
@b.add_function()
def convert_by_unpacking(size):
[*iter(range(size))]
@b.add_arguments('Convert an iterator to a list')
def argument_provider():
for exp in range(2, 22):
size = 2**exp
yield size, size
r = b.run()
r.plot()
正如您所看到的,很难区分通过构造函数进行的转换和通过解包进行的转换,通过列表理解进行的转换是“最慢”的方法。
我还使用以下简单的脚本在不同的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 Notebook(或脚本)的子进程执行,size参数将通过命令行参数传递,脚本结果将从标准输出中获取。
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%。
其他回答
list(your_iterator)
从python 3.5开始,你可以使用* iterable解包操作符:
user_list = [*your_iterator]
但是python的方法是:
user_list = list(your_iterator)
@Robino建议添加一些有意义的测试,所以这里有一个简单的基准测试,有3种可能的方法(可能是最常用的)来将迭代器转换为列表:
按类型构造函数 列表(my_iterator) 通过拆包 (* my_iterator) 使用列表推导式 [e for my_iterator中的e]
我一直在使用simple_bechmark库:
from simple_benchmark import BenchmarkBuilder
from heapq import nsmallest
b = BenchmarkBuilder()
@b.add_function()
def convert_by_type_constructor(size):
list(iter(range(size)))
@b.add_function()
def convert_by_list_comprehension(size):
[e for e in iter(range(size))]
@b.add_function()
def convert_by_unpacking(size):
[*iter(range(size))]
@b.add_arguments('Convert an iterator to a list')
def argument_provider():
for exp in range(2, 22):
size = 2**exp
yield size, size
r = b.run()
r.plot()
正如您所看到的,很难区分通过构造函数进行的转换和通过解包进行的转换,通过列表理解进行的转换是“最慢”的方法。
我还使用以下简单的脚本在不同的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 Notebook(或脚本)的子进程执行,size参数将通过命令行参数传递,脚本结果将从标准输出中获取。
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%。