views:

823

answers:

3

Python provides a nice method for getting length of an eager iterable, len(x) that is. But I couldn't find anything similar for lazy iterables represented by generator comprehensions and functions. Of course, it is not hard to write something like:

def iterlen(x):
  n = 0
  try:
    while True:
      next(x)
      n += 1
  except StopIteration: pass
  return n

But I can't get rid of a feeling that I'm reimplementing a bicycle.

(While I was typing the function, a thought struck my mind: maybe there really is no such function, because it "destroys" its argument. Not an issue for my case, though).

P.S.: concerning the first answers - yes, something like len(list(x)) would work too, but that drastically increases the usage of memory.

P.P.S.: re-checked... Disregard the P.S., seems I made a mistake while trying that, it works fine. Sorry for the trouble.

+9  A: 

There isn't one because you can't do it in the general case - what if you have a lazy infinite generator? For example:

def fib():
    a, b = 0, 1
    while True:
        a, b = b, a + b
        yield a

This never terminates but will generate the Fibonacci numbers. You can get as many Fibanacci numbers as you want by calling next().

If you really need to know the number of items there are, then you can't iterate through them linearly one time anyways, so just use a different data structure such as a regular list.

Adam Rosenfield
I'm not sure I believe/accept the explanation. `sum` takes an iterable, even though that iterable might be infinite and hence "you can't do it in the general case" any more than you can do len in the general case. Perhaps a more likely rationale is that people "expect" `len` to be O(1), which it isn't for a general iterable?
Steve Jessop
+3  A: 

Duplicate question, even with almost the same example code:

Is there any built-in way to get the length of an iterable in Python?

dF
+4  A: 

You can use enumerate() to loop through the generated data stream, then return the last number -- the number of items.

I tried to use itertools.count() with itertools.izip() but no luck. This is the best/shortest answer I've come up with:

#!/usr/bin/python

import itertools

def func():
    for i in 'yummy beer':
        yield i

def icount(ifunc):
    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

Kamil Kisiel's solution is way better:

def count_iterable(i):
    return sum(1 for e in i)