如何从迭代器创建元素列表(将迭代器转换为列表)?

275 投票
3 回答
191138 浏览
提问于 2025-04-16 04:34

给定一个迭代器 user_iterator,我该如何遍历这个迭代器,得到它产生的对象列表呢?

我有这段代码,看起来是可以工作的:

user_list = [user for user in user_iterator]

但是有没有更快、更好或者更正确的方法呢?

3 个回答

39

Python 3.5 开始,你可以使用 * 这个叫做可迭代解包运算符的东西:

user_list = [*your_iterator]

但是 更符合Python风格的方法 是:

user_list  = list(your_iterator)
47

@Robino 建议添加一些有意义的测试,所以这里有一个简单的基准测试,比较三种可能的方式(可能是最常用的)将迭代器转换为列表:

  1. 通过类型构造器

    list(my_iterator)

  2. 通过解包

    [*my_iterator]

  3. 使用列表推导式

    [e for e in my_iterator]

我使用了simple_benchmark库来进行测试:

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()

enter image description here

从结果来看,通过构造器和解包的转换几乎没有区别,而通过列表推导式的转换是最“慢”的方法。


我还测试了不同的 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(或脚本)中执行,大小参数会通过命令行参数传递,脚本的结果会从标准输出中获取。

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_

enter image description here

你可以从这里获取我的完整笔记本。

在我的测试中,大多数情况下,解包的速度更快,但差异非常小,结果可能会因运行而异。再次强调,列表推导式的方法是最慢的,实际上,其他两种方法的速度快了大约 60%。

473
list(your_iterator)

当然可以!请把你想要翻译的内容发给我,我会帮你用简单易懂的语言解释清楚。

撰写回答