méthode qui est significativement plus rapide que sum(1 for i in it)
lorsque le itérables peut être long (et non de manière significative plus lente lorsque la itérables est courte), tout en maintenant la mémoire fixe le comportement au-dessus (contrairement len(list(it))
) pour éviter raclée de swap et les frais généraux de réaffectation 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 réalise la boucle dans le code C sur CPython (deque
, count
et zip
sont tous mis en oeuvre dans C); éviter l'exécution de code octet par boucle est généralement la clé de la performance dans CPython.
Il est étonnamment difficile de trouver des cas de test juste pour comparer les performances (list
triche en utilisant __length_hint__
qui n'est pas susceptible d'être disponible pour iterables d'entrée arbitraires, itertools
fonctions qui ne fournissent pas __length_hint__
ont souvent des modes de fonctionnement spéciaux qui fonctionnent plus rapide lorsque la valeur retournée sur chaque boucle est libérée avant que la valeur suivante ne soit demandée, ce que fera deque
avec maxlen=0
). Le cas de test j'était de créer une fonction de générateur qui prendrait une entrée et retourner un générateur de niveau C qui ne disposaient pas des optimisations de conteneurs spéciaux itertools
de retour ou __length_hint__
, en utilisant Python 3.3 de yield from
:
def no_opt_iter(it):
yield from it
Ensuite, en utilisant ipython
%timeit
magie (en remplaçant différentes constantes pour 100):
>>> %%timeit -r5 fakeinput = (0,) * 100
... ilen(no_opt_iter(fakeinput))
Lorsque l'entrée est pas assez grand que len(list(it))
causerait des problèmes de mémoire, sur une machine Linux x64 Python 3.5, ma solution prend environ 50% plus tha n def ilen(it): return len(list(it))
, quelle que soit la longueur d'entrée.
Pour les plus petits des intrants, les coûts d'installation pour appeler deque
/zip
/count
/next
signifie qu'il faut infinitésimale plus de cette façon que def ilen(it): sum(1 for x in it)
(environ 200 ns plus sur ma machine pour une longueur 0 entrée, qui est de 33% augmentation par rapport à l'approche simple sum
), mais pour les entrées plus longues, il est utilisé environ la moitié du temps par élément supplémentaire; pour la longueur 5 entrées, le coût est équivalent, et quelque part dans la gamme de longueur 50-100, le surcoût initial 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 vous vous souciez de la vitesse plus que de la brièveté, utilisez cette solution. Si les entrées sont bornées et petites, len(list(it))
est probablement la meilleure, et si elles sont illimitées, mais que la simplicité/concision compte, vous utiliserez sum(1 for x in it)
.
S'il vous plaît ne pas utiliser '_' comme nom de variable, parce que (1) il a tendance à confondre les gens, en leur faisant croire que c'est une sorte de syntaxe particulière, (2) entre en collision avec' _' en l'interpréteur interactif et (3) se heurte à l'alias gettext commun. –
@Sven: J'utilise '_' tout le temps pour les variables inutilisées (une habitude de programmation Prolog et Haskell). (1) est une raison pour demander cela en premier lieu. Je n'ai pas considéré (2) et (3), merci de les signaler! –
dupliqué: http://stackoverflow.com/questions/390852/is-there-any-built-in-way-to-get-the-length-of-an-iterable-in-python – tokland