将迭代器转换为列表的最快方法

sys*_*out 165 python iterator list-comprehension

有一个iterator对象,有没有比列表理解更快,更好或更正确的东西来获取迭代器返回的对象列表?

user_list = [user for user in user_iterator]
Run Code Online (Sandbox Code Playgroud)

mik*_*obi 296

list(your_iterator)
Run Code Online (Sandbox Code Playgroud)

  • @systempuntoout它完全在C中运行.列表理解是在python中.当然它运行得更快. (8认同)
  • 实际上,几乎总是快一点.而且,更加明显. (5认同)
  • 在我的快速测试中,`[*your_iterator]`的速度似乎是`list(your_iterator)`的两倍.这一般是正确的,还是只是一个特定的场合?(我使用`map`作为迭代器.) (4认同)
  • 我仍然完全讨厌python中没有更好的方法.编辑表达式的两面只是为了能够对其进行切片或索引,这很繁琐.(在python3中非常常见,如果它是像zip这样的纯表达式,或者是带有纯函数的映射) (3认同)
  • @Bachsau:诚然,它相当不错,但与 Bash 脚本相比,您可以通过严格在当前命令右侧附加管道和另一个过滤器命令来操作当前输出。很糟糕的是,对于这样一个微小的区别(迭代器与物化列表),您经常必须将光标向后移动。 (2认同)

rus*_*ro1 17

@Robino 建议添加一些有意义的测试,因此这是将迭代器转换为列表的 3 种可能方法(可能是最常用的方法)之间的简单基准测试:

  1. 按类型构造函数

list(my_iterator)

  1. 通过开箱

[*my_iterator]

  1. 使用列表理解

[e for e in my_iterator]

我一直在使用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()
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

正如您所看到的,很难区分构造函数的转换和解包的转换,列表推导式的转换是“最慢”的方法。


我还使用以下简单脚本在不同的 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))
Run Code Online (Sandbox Code Playgroud)

脚本将通过 Jupyter Notebook(或脚本)的子进程执行,大小参数将通过命令行参数传递,脚本结果将从标准输出中获取。

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_
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

你可以从这里得到我完整的笔记本。

在大多数情况下,在我的测试中,解包显示速度更快,但差异很小,结果可能会从一次运行到另一次运行。同样,理解方法是最慢的,实际上,其他 2 种方法最多快 60%。


rus*_*ro1 5

python 3.5开始, 您可以使用*可迭代的拆包运算符:

user_list = [*your_iterator]
Run Code Online (Sandbox Code Playgroud)

pythonic的方法是:

user_list  = list(your_iterator)
Run Code Online (Sandbox Code Playgroud)

  • 请发布速度测试结果以获得更多积分! (2认同)
  • @Robino 感谢您的建议,您可以查看/sf/answers/4515855781/ (2认同)