python - has - Longitud de salida del generador
python iter (8)
Esto es un truco, pero si realmente quieres tener un trabajo lento en un iterable general (consumiéndolo en el camino), puedes crear tu propia versión de len
.
La función len
es esencialmente equivalente a la siguiente (aunque las implementaciones suelen proporcionar algunas optimizaciones para evitar la búsqueda adicional):
def len(iterable):
return iterable.__len__()
Por lo tanto, podemos definir nuestro new_len
para probar eso, y si __len__
no existe, cuente el número de elementos consumiendo el iterable:
def new_len(iterable):
try:
return iterable.__len__()
except AttributeError:
return sum(1 for _ in iterable)
Lo anterior funciona en Python 2/3, y (hasta donde yo sé) debería cubrir todo tipo de iterable concebible.
Python proporciona un buen método para obtener la longitud de un iterable ansioso, len(x)
que es. Pero no pude encontrar nada similar para los iterables perezosos representados por las funciones y las funciones del generador. Por supuesto, no es difícil escribir algo como:
def iterlen(x):
n = 0
try:
while True:
next(x)
n += 1
except StopIteration: pass
return n
Pero no puedo deshacerme de la sensación de que estoy volviendo a implementar una bicicleta.
(Mientras estaba escribiendo la función, un pensamiento me vino a la mente: tal vez realmente no exista tal función, porque "destruye" su argumento. Sin embargo, no es un problema para mi caso).
PD: con respecto a las primeras respuestas, sí, algo como len(list(x))
también funcionaría, pero eso aumenta drásticamente el uso de la memoria.
PPS: revisado ... Ignore el PS, parece que cometí un error al intentarlo, funciona bien. Lo siento por la molestia.
La forma más fácil es probablemente solo sum(1 for _ in gen)
donde gen es tu generador.
No hay uno porque no puedes hacerlo en el caso general, ¿y si tienes un generador infinito perezoso? Por ejemplo:
def fib():
a, b = 0, 1
while True:
a, b = b, a + b
yield a
Esto nunca termina pero generará los números de Fibonacci. Puede obtener tantos números de Fibonacci como desee llamando al next()
.
Si realmente necesita saber la cantidad de elementos que existen, entonces no puede iterar linealmente en ellos una vez, así que simplemente use una estructura de datos diferente, como una lista normal.
Por definición, solo un subconjunto de generadores regresará después de un cierto número de argumentos (tienen una longitud predefinida), e incluso entonces, solo un subconjunto de estos generadores finitos tiene un final predecible (el acceso al generador puede tener efectos secundarios que podría detener el generador antes).
Si desea implementar métodos de longitud para su generador, primero debe definir lo que considera la "longitud" (¿es la cantidad total de elementos? ¿La cantidad de elementos restantes?), Luego ajuste su generador en una clase. Aquí hay un ejemplo:
class MyFib(object):
"""
A class iterator that iterates through values of the
Fibonacci sequence, until, optionally, a maximum length is reached.
"""
def __init__(self, length):
self._length = length
self._i = 0
def __iter__(self):
a, b = 0, 1
while not self._length or self._i < self._length:
a, b = b, a + b
self._i += 1
yield a
def __len__(self):
"This method returns the total number of elements"
if self._length:
return self._length
else:
raise NotImplementedError("Infinite sequence has no length")
# or simply return None / 0 depending
# on implementation
Aquí es cómo usarlo:
In [151]: mf = MyFib(20)
In [152]: len(mf)
Out[152]: 20
In [153]: l = [n for n in mf]
In [154]: len(l)
Out[154]: 20
In [155]: l
Out[155]:
[1,
1,
2,
...
6765]
In [156]: mf0 = MyFib(0)
In [157]: len(mf0)
---------------------------------------------------------------------------
NotImplementedError Traceback (most recent call last)
<ipython-input-157-2e89b32ad3e4> in <module>()
----> 1 len(mf0)
/tmp/ipython_edit_TWcV1I.py in __len__(self)
22 return self._length
23 else:
---> 24 raise NotImplementedError
25 # or simply return None / 0 depending
26 # on implementation
NotImplementedError:
In [158]: g = iter(mf0)
In [159]: l0 = [g.next(), g.next(), g.next()]
In [160]: l0
Out[160]: [1, 1, 2]
Pruebe el paquete more_itertools
para una solución simple. Ejemplo:
>>> import more_itertools
>>> it = iter("abcde") # sample generator
>>> it
<str_iterator at 0x4ab3630>
>>> more_itertools.ilen(it)
5
Vea esta publicación para otro ejemplo aplicado.
Puede usar enumerate () para recorrer el flujo de datos generado, luego devolver el último número: el número de elementos.
Traté de usar itertools.count () con itertools.izip () pero no tuve suerte. Esta es la mejor / más corta respuesta que he encontrado:
#!/usr/bin/python
import itertools
def func():
for i in ''yummy beer'':
yield i
def icount(ifunc):
size = -1 # for the case of an empty iterator
for size, _ in enumerate(ifunc()):
pass
return size + 1
print list(func())
print ''icount'', icount(func)
# [''y'', ''u'', ''m'', ''m'', ''y'', '' '', ''b'', ''e'', ''e'', ''r'']
# icount 10
La solución de Kamil Kisiel es mucho mejor:
def count_iterable(i):
return sum(1 for e in i)
Use reduce (función, iterable [, inicializador]) para una solución puramente funcional con memoria eficiente:
>>> iter = "This string has 30 characters."
>>> reduce(lambda acc, e: acc + 1, iter, 0)
30
def count(iter):
return sum(1 for _ in iter)
O mejor aún:
def count(iter):
try:
return len(iter)
except TypeError:
return sum(1 for _ in iter)
Si no es iterable, arrojará un TypeError
.
O bien, si desea contar algo específico en el generador:
def count(iter, key=None):
if key:
if callable(key):
return sum(bool(key(x)) for x in iter)
return sum(x == key for x in iter)
try:
return len(iter)
except TypeError:
return sum(1 for _ in iter)