Método que es significativamente más rápido que sum(1 for i in it)
cuando el iterable puede ser largo (y no significativamente más lenta cuando el iterable es corto), mientras se mantiene el comportamiento de sobrecarga de memoria fija (a diferencia de len(list(it))
) para evitar thrashing de intercambio y los gastos generales reasignación para las entradas más grandes:
# 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)
Como len(list(it))
que realiza el bucle en el código C en CPython (deque
, count
y zip
se implementan en C); evitar la ejecución del código de bytes por ciclo suele ser la clave del rendimiento en CPython.
Es sorprendentemente difícil llegar a casos de prueba justos para comparar el rendimiento (list
tramposos que utilizan __length_hint__
que no es probable que esté disponible para iterables de entrada arbitrarias, itertools
funciones que no proporcionan __length_hint__
menudo tienen modos de funcionamiento especiales que funcionan más rápido cuando se libera el valor devuelto en cada ciclo antes de que se solicite el siguiente valor, lo que hará deque
con maxlen=0
). El caso de prueba que utilicé fue crear una función de generador que tomaría una entrada y devolver un generador de nivel C que carecía especial itertools
optimizaciones Devolución de envases o __length_hint__
, usando Python 3.3 de yield from
:
def no_opt_iter(it):
yield from it
Luego, utilizando ipython
%timeit
magia (sustituyendo diferentes constantes de 100):
>>> %%timeit -r5 fakeinput = (0,) * 100
... ilen(no_opt_iter(fakeinput))
Cuando la entrada no es lo suficientemente grande como para que len(list(it))
podría causar problemas de memoria, en un Linux ejecutando Python 3.5 x64, mi solución tarda aproximadamente un 50% más tha n def ilen(it): return len(list(it))
, independientemente de la longitud de entrada.
Para la más pequeña de las entradas, los costos de instalación para llamar deque
/zip
/count
/next
significa que se necesita infinitesimalmente ya de esta manera que def ilen(it): sum(1 for x in it)
(alrededor de 200 ns más en mi máquina para una entrada de longitud 0, que es un 33% aumentar sobre el enfoque simple sum
), pero para entradas más largas, se ejecuta en aproximadamente la mitad del tiempo por elemento adicional; para las entradas de longitud 5, el costo es equivalente, y en algún lugar del rango de 50-100, la sobrecarga inicial es imperceptible en comparación con el trabajo real; el enfoque sum
toma aproximadamente el doble de tiempo.
Básicamente, si el uso de la memoria es importante o las entradas no tienen un tamaño limitado y usted se preocupa por la velocidad más que la brevedad, utilice esta solución. Si las entradas son limitadas y más bien pequeñas, len(list(it))
es probablemente la mejor, y si son ilimitadas, pero el conteo de simplicidad/brevedad, usaría sum(1 for x in it)
.
Por favor, no use '_' como nombre de variable, debido a que (1) tiende a confundir a la gente, haciéndoles creer que esto es una especie de sintaxis especial, (2) choca con' _' en el intérprete interactivo y (3) colisiona con el alias gettext común. –
@Sven: uso '_' todo el tiempo para las variables no utilizadas (un hábito de la programación de Prolog y Haskell). (1) es una razón para preguntar esto en primer lugar. No consideré (2) y (3), ¡gracias por señalarlos! –
duplicado: http://stackoverflow.com/questions/390852/is-here-any-built-in-way-to-get-the-length-of-an-iterable-in-python – tokland