web-dev-qa-db-fra.com

Quel est le moyen le plus court pour compter le nombre d'articles dans un générateur / itérateur?

Si je veux le nombre d'éléments dans un itérable sans se soucier des éléments eux-mêmes, quelle serait la façon Pythonique d'obtenir cela? En ce moment, je définirais

def ilen(it):
    return sum(itertools.imap(lambda _: 1, it))    # or just map in Python 3

mais je comprends que lambda est sur le point d'être considéré comme nuisible, et lambda _: 1 n'est certainement pas joli.

(Le cas d'utilisation de ceci est le comptage du nombre de lignes dans un fichier texte correspondant à une expression régulière, c'est-à-dire grep -c.)

59
Fred Foo

La manière habituelle est

sum(1 for i in it)
133
Sven Marnach

Méthode significativement plus rapide que sum(1 for i in it) lorsque l'itérable peut être long (et non plus lentement lorsque l'itérable est court), tout en conservant un comportement de surcharge de mémoire fixe (contrairement à len(list(it))) pour éviter le débordement de swap et la réallocation frais généraux pour les entrées plus importantes:

# On Python 2 only, get Zip that lazily generates results instead of returning list
from future_builtins import Zip

from collections import deque
from itertools import count

def ilen(it):
    # Make a stateful counting iterator
    cnt = count()
    # Zip it with the input iterator, then drain until input exhausted at C level
    deque(Zip(it, cnt), 0) # cnt must be second Zip arg to avoid advancing too far
    # Since count 0 based, the next value is the count
    return next(cnt)

Comme len(list(it)), il exécute la boucle en code C sur CPython (deque, count et Zip sont tous implémentés en C); éviter l'exécution de code octet par boucle est généralement la clé des performances dans CPython.

Il est étonnamment difficile de trouver des cas de test équitables pour comparer les performances (list astuces en utilisant __length_hint__ Qui n'est probablement pas disponible pour les itérables d'entrée arbitraires, itertools fonctions qui ne ne pas fournir __length_hint__ ont souvent des modes de fonctionnement spéciaux qui fonctionnent plus rapidement lorsque la valeur retournée sur chaque boucle est libérée avant que la valeur suivante ne soit demandée, ce que deque avec maxlen=0 fera ). Le cas de test que j'ai utilisé était de créer une fonction de générateur qui prendrait une entrée et retournerait un générateur de niveau C qui manquait de itertools optimisations de conteneur de retour ou __length_hint__, En utilisant Python 3.3's yield from:

def no_opt_iter(it):
    yield from it

Puis en utilisant la magie ipython%timeit (En remplaçant 100 par différentes constantes):

>>> %%timeit -r5 fakeinput = (0,) * 100
... ilen(no_opt_iter(fakeinput))

Lorsque l'entrée n'est pas suffisamment grande pour que len(list(it)) entraîne des problèmes de mémoire, sur une boîte Linux exécutant Python 3,5 x64, ma solution prend environ 50% de plus que def ilen(it): return len(list(it)), quelle que soit la longueur d'entrée.

Pour la plus petite des entrées, la configuration coûte pour appeler deque/Zip/count/next signifie que cela prend infiniment plus de temps de cette façon que def ilen(it): sum(1 for x in it) (environ 200 ns de plus sur ma machine pour une entrée de longueur 0, ce qui représente une augmentation de 33% par rapport à la simple approche sum), mais pour des entrées plus longues, elle s'exécute environ la moitié du temps par élément supplémentaire; pour les entrées de longueur 5, le coût est équivalent, et quelque part dans la plage de longueur 50-100, la surcharge initiale est imperceptible par rapport au travail réel; l'approche sum prend environ deux fois plus de temps.

Fondamentalement, si l'utilisation de la mémoire est importante ou si les entrées n'ont pas de taille limitée et que la rapidité est plus importante que la concision, utilisez cette solution. Si les entrées sont limitées et de petite taille, len(list(it)) est probablement la meilleure solution, et si elles ne sont pas limitées, mais la simplicité/concision compte, vous utiliseriez sum(1 for x in it).

27
ShadowRanger

Un moyen court est:

def ilen(it):
    return len(list(it))

Notez que si vous générez un lot d'éléments (par exemple, des dizaines de milliers ou plus), les mettre dans une liste peut devenir un problème de performances. Cependant, il s'agit d'une simple expression de l'idée selon laquelle les performances n'auront pas d'importance dans la plupart des cas.

7
Greg Hewgill

more_itertools est une bibliothèque tierce qui implémente un outil ilen . pip install more_itertools

import more_itertools as mit


mit.ilen(x for x in range(10))
# 10
4
pylang

J'aime le paquet cardinality pour cela, il est très léger et essaie d'utiliser l'implémentation la plus rapide possible en fonction de l'itérable.

Usage:

>>> import cardinality
>>> cardinality.count([1, 2, 3])
3
>>> cardinality.count(i for i in range(500))
500
>>> def gen():
...     yield 'hello'
...     yield 'world'
>>> cardinality.count(gen())
2
1
Erwin Mayer

Ce seraient mes choix l'un ou l'autre:

print(len([*gen]))
print(len(list(gen)))
1
prosti