Avoir un iterator
objet, y a-t-il quelque chose de plus rapide, meilleur ou plus correct qu'une compréhension de liste pour obtenir une liste des objets retournés par l'itérateur?
user_list = [user for user in user_iterator]
python
iterator
list-comprehension
systempuntoout
la source
la source
[*iterator]
.Réponses:
la source
[*your_iterator]
semblé être environ deux fois plus rapide quelist(your_iterator)
. Est-ce généralement vrai ou était-ce juste une occasion spécifique? (J'ai utilisé unmap
itérateur.)depuis python 3.5, vous pouvez utiliser l'
*
opérateur de décompression itérable:mais la manière pythonique de le faire est:
la source
@Robino suggérait d'ajouter quelques tests qui ont du sens, voici donc un simple benchmark entre 3 façons possibles (peut-être les plus utilisées) de convertir un itérateur en liste:
list(my_iterator)
[*my_iterator]
[e for e in my_iterator]
J'utilise simple_bechmark bibliothèque
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()
Comme vous pouvez le voir, il est très difficile de faire une différence entre la conversion par le constructeur et la conversion par décompression, la conversion par compréhension de liste est l'approche la plus «lente».
J'ai également testé différentes versions de Python (3.6, 3.7, 3.8, 3.9) en utilisant le script simple suivant:
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))
Le script sera exécuté via un sous-processus à partir d'un Jupyter Notebook (ou d'un script), le paramètre size sera passé via des arguments de ligne de commande et les résultats du script seront extraits de la sortie standard.
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_
Vous pouvez obtenir mon cahier complet d' ici .
Dans la plupart des cas, dans mes tests, le déballage s'avère plus rapide, mais la différence est si petite que les résultats peuvent changer d'une exécution à l'autre. Encore une fois, l'approche de compréhension est la plus lente, en fait, les 2 autres méthodes sont jusqu'à ~ 60% plus rapides.
la source