palabras iteradores generadores generador funcion explicacion creacion codigo python iterator generator iterable

iteradores - python funcion yield



¿Cuál es la forma más corta de contar la cantidad de elementos en un generador/iterador? (5)

Si quiero la cantidad de elementos en un iterable sin preocuparme por los elementos en sí, ¿cuál sería la manera pitónica de conseguirlo? En este momento, definiría

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

pero entiendo que lambda está cerca de ser considerado dañino, y lambda _: 1 ciertamente no es bonito.

(El caso de uso de esto es contar el número de líneas en un archivo de texto que coincida con una expresión regular, es decir, grep -c ).


La forma habitual es

sum(1 for i in it)


Método que es significativamente más rápido que la sum(1 for i in it) cuando el iterable puede ser largo (y no significativamente más lento cuando el iterable es corto), mientras mantiene el comportamiento fijo de memoria fija (a diferencia de len(list(it)) ) para evitar swap sobrecarga de reasignación y reasignación para insumos 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)

Al igual que len(list(it)) realiza el ciclo en código C en CPython ( deque , count y zip están todos implementados 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 encontrar casos de prueba justos para comparar el rendimiento ( list trucos usando __length_hint__ que probablemente no estará disponible para los iterables de entrada arbitrarios, itertools funciones de los itertools que no proporcionan __length_hint__ menudo tienen modos de operación especiales que funcionan más rápido cuando el valor devuelto en cada ciclo se libera liberado antes de que se solicite el siguiente valor, lo que deque con maxlen=0 hará). El caso de prueba que utilicé fue para crear una función de generador que tomaría una entrada y devolvería un generador de nivel C que carecía de itertools retorno especiales optimizaciones de contenedor de retorno o __length_hint__ , utilizando el yield from Python 3.3 yield from :

def no_opt_iter(it): yield from it

Luego, usando ipython %timeit magic (sustituyendo constantes diferentes por 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)) cause problemas de memoria, en una caja de Linux que ejecute Python 3.5 x64, mi solución tarda aproximadamente un 50% más que def ilen(it): return len(list(it)) , independientemente de la longitud de entrada.

Para las entradas más pequeñas, los costos de configuración para llamar a deque / zip / count / next significa que demora infinitesimalmente más tiempo que def ilen(it): sum(1 for x in it) (aproximadamente 200 ns más en mi máquina para un entrada de longitud 0, que es un aumento del 33% sobre el enfoque de sum simple), 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 de 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 por la brevedad, use esta solución. Si las entradas son limitadas y 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) .


Me gusta el paquete de cardinality para esto, es muy liviano y trata de usar la implementación más rápida posible según el iterable.

Uso:

>>> 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


Un corto camino es:

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

Tenga en cuenta que si está generando muchos elementos (por ejemplo, decenas de miles o más), ponerlos en una lista puede convertirse en un problema de rendimiento. Sin embargo, esta es una expresión simple de la idea de que el rendimiento no va a importar en la mayoría de los casos.


more_itertools es una biblioteca de terceros que implementa una herramienta ilen . pip install more_itertools

import more_itertools as mit mit.ilen(x for x in range(10)) # 10