Search code examples
pythonstringlistenumerate

Is there more to enumerate() than just zip(range(len()))?


Everything in Python is there for a reason. All systems powered by Python depend on something like 50 built-in functions, most of which are extremely useful and unique, like format(), len(), list(), or range(). I can't understand why enumerate() exists.

It's been introduced in PEP 279 (2002) and was kept until now. I really don't understand why it exists because it can be done using other more important built-in functions in 2-3 characters more. From the Python Docs:

seasons = ['Spring', 'Summer', 'Fall', 'Winter']
for i in enumerate(seasons):
    print(i)

An implementation using more important built-in functions is this:

for i in zip(range(len(seasons)), seasons):
    print(i)
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]

These two are the same, and we all know how critically important zip() and range() are. So why add a built-in function that would seemingly add no value beyond these two?

In the Python Docs, here is the equivalent of enumerate():

def enumerate(sequence, start=0):
    n = start
    for elem in sequence:
        yield n, elem
        n += 1

The bottom line: I'm wondering if enumerate() has some unique capabilities that I'm not seeing.


Solution

  • Because not every iterable has a length.

    >>> def countdown(x):
    ...     while x >= 0:
    ...         yield x
    ...         x -= 1
    ...         
    >>> down = countdown(3)
    >>> len(down)
    Traceback (most recent call last):
    [...]
    TypeError: object of type 'generator' has no len()
    >>> enum = enumerate(down)
    >>> next(enum)
    (0, 3)
    >>> next(enum)
    (1, 2)
    

    This is a trivial example, of course. But I could think of lots of real world objects where you can't reasonably pre-compute a length. Either because the length is infinite (see itertools.count) or because the object you are iterating over does itself not know when the party is over.

    Your iterator could be fetching chunks of data from a remote database of unknown size or to which the connection might get lost without warning. Or it could process user input.

    def get_user_input():
         while True:
            i = input('input value or Q to quit: ')
            if i == 'Q':
                break
            yield i
    

    You cannot get the length of get_user_input(), but you can enumerate all inputs as you fetch them via next (or iteration).