systempuntoout
Dado un iterador user_iterator
¿cómo puedo iterar sobre el iterador una lista de los objetos producidos?
Tengo este código, que parece funcionar:
user_list = [user for user in user_iterator]
Pero, ¿hay algo más rápido, mejor o más correcto?
mikerobi
list(your_iterator)
-
En realidad, casi siempre un poco más rápido. Además, mucho más evidente.
– Thomas Wouters
24/09/2010 a las 20:52
-
@systempuntoout Se ejecuta completamente en C. La lista de comprensión está en python. Por supuesto, corre más rápido.
– aaronasterling
24 de septiembre de 2010 a las 22:24
-
Todavía odio totalmente que no haya una mejor manera en Python. Es tedioso tener que editar ambos lados de una expresión solo para poder dividirla o indexarla. (muy común en python3, si es una expresión pura como zip o un mapa con una función pura)
– Jo So
24 de octubre de 2015 a las 5:29
-
En mi prueba rápida,
[*your_iterator]
parecía ser el doble de rápido quelist(your_iterator)
. ¿Es esto generalmente cierto, o fue solo una ocasión específica? (Usé unmap
como iterador.)–Neinstein
10/09/2018 a las 21:49
-
@Bachsau: es cierto que es bastante bueno, pero compárelo con las secuencias de comandos de Bash, donde puede manipular la salida actual agregando una tubería y otro comando de filtro estrictamente a la derecha del comando actual. Apesta que para una distinción tan pequeña (iterador vs lista materializada) a menudo tengas que mover el cursor hacia atrás.
– Jo So
25 de septiembre de 2019 a las 13:19
kederrac
desde pitón 3.5 puedes usar *
operador de desempaquetado iterable:
user_list = [*your_iterator]
pero la forma pitónica de hacerlo es:
user_list = list(your_iterator)
-
¡Por favor publique los resultados de una prueba de velocidad para obtener más puntos!
– Robino
15 oct 2020 a las 8:34
-
@Robino gracias por la sugerencia, puede consultar stackoverflow.com/a/64512225/3161575
– kederrac
24 de octubre de 2020 a las 10:12
kederrac
@Robino estaba sugiriendo agregar algunas pruebas que tienen sentido, por lo que aquí hay un punto de referencia simple entre 3 formas posibles (quizás las más utilizadas) para convertir un iterador en una lista:
-
por tipo constructor
list(my_iterator)
-
al desempacar
[*my_iterator]
-
usando la comprensión de listas
[e for e in my_iterator]
he estado usando simple_bechmark biblioteca:
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()
Como puede ver, es muy difícil hacer una diferencia entre la conversión por el constructor y la conversión por desempaquetado, la conversión por comprensión de lista es el enfoque “más lento”.
También he estado probando diferentes versiones de Python (3.6, 3.7, 3.8, 3.9) usando el siguiente script simple:
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))
La secuencia de comandos se ejecutará a través de un subproceso de un Jupyter Notebook (o una secuencia de comandos), el parámetro de tamaño se pasará a través de argumentos de línea de comandos y los resultados de la secuencia de comandos se tomarán de la salida estándar.
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_
Puedes obtener mi libreta completa de aquí.
En la mayoría de los casos, en mis pruebas, el desempaquetado muestra ser más rápido, pero la diferencia es tan pequeña que los resultados pueden cambiar de una ejecución a otra. Nuevamente, el enfoque de comprensión es el más lento; de hecho, los otros 2 métodos son hasta ~ 60 % más rápidos.
Antes de optimizar esto, asegúrese de haber realizado algunos perfiles para demostrar que este es realmente el cuello de botella.
– S. Lott
24/09/2010 a las 20:52
@S.Lott. Normalmente estoy de acuerdo con esa actitud pero, en este caso, debería optimizarse estilísticamente, lo que, como suele ser el caso con Python, también optimizará la velocidad.
– aaronasterling
24 de septiembre de 2010 a las 21:10
El OP no dijo nada sobre tener un cuello de botella. Es una pregunta general perfectamente adecuada con una respuesta simple, no necesita depender de una aplicación específica que se pueda ejecutar a través de un generador de perfiles.
–Ken Williams
19 de marzo de 2017 a las 3:49
La forma más compacta es
[*iterator]
.– Challenger5
27 de marzo de 2017 a las 6:04