Python’s enumerate() Function Demystified How and why you should use the built-in enumerate function in Python to write cleaner and more Pythonic loops.

Python’s enumerate function is a mythical beast—it’s hard to summarize its purpose and usefulness in a single sentence. And yet, it’s a super useful feature that many beginners and even intermediate Pythonistas are blissfully unaware of. Basically, enumerate() allows you to loop over a collection of items while keeping track of the current item’s index in a counter variable. Let’s take a look at an example: names = [ 'Bob' , 'Alice' , 'Guido' ] for index , value in enumerate ( names ): print ( f '{index}: {value}' ) This leads to the following output: 0 : Bob 1 : Alice 2 : Guido As you can see, this iterated over the names list and generated an index for each element by increasing a counter variable starting at zero. [ If you’re wondering about the f'...' string syntax I used in the above example, this is a new string formatting technique available in Python 3.6 and above. ]

Make Your Loops More Pythonic With enumerate() Now why is keeping a running index with the enumerate function useful? I noticed that new Python developers coming from a C or Java background sometimes use the following range(len(...)) antipattern to keep a running index while iterating over a list with a for -loop: # HARMFUL: Don't do this for i in range ( len ( my_items )): print ( i , my_items [ i ]) By using the enumerate function skillfully, like I showed you in the “names” example above, you can make this looping construct much more “Pythonic” and idiomatic. There’s usually no need to generate element indexes manually in Python—you simply leave all of this work to the enumerate function. And as a result your code will be easier to read and less vulnerable to typos.

Changing the Starting Index Another useful feature is the ability to choose the starting index for the enumeration. The enumerate() function accepts an optional argument which allows you to set the initial value for its counter variable: names = [ 'Bob' , 'Alice' , 'Guido' ] for index , value in enumerate ( names , 1 ): print ( f '{index}: {value}' ) In the above example I changed the function call to enumerate(names, 1) and the extra 1 argument now starts the index at one instead of zero: 1 : Bob 2 : Alice 3 : Guido Voilà, this is how you switch from zero-based indexing to starting with index 1 (or any other int , for that matter) using Python’s enumerate() function.

How enumerate() Works Behind The Scenes You might be wondering how the enumerate function works behind the scenes. Part of it’s magic lies in the fact that enumerate is implemented as a Python iterator. This means that element indexes are generated lazily (one by one, just-in-time), which keeps memory use low and keeps this construct so fast. Let’s play with some more code to demonstrate what I mean: >>> names = [ 'Bob' , 'Alice' , 'Guido' ] >>> enumerate ( names ) < enumerate object at 0x1057f4120 > In the above code snippet I set up the same enumeration you’ve already seen in the previous examples. But instead of immediately looping over the result of the enumerate call I’m just displaying the returned object on the Python console. As you can see, it’s an “enumerate object.” This is the actual iterator. And like I said, it generates its output elements lazily and one by one when they’re requested. In order to retrieve those “on demand” elements so we can inspect them, I’m going to call the built-in list() function on the iterator: >>> list ( enumerate ( names )) [( 0 , 'Bob' ), ( 1 , 'Alice' ), ( 2 , 'Guido' )] For each element in the input list ( names ) the iterator returned by enumerate() produces a tuple of the form (index, element) . In your typical for-in loop you’ll use this to your advantage by leveraging Python’s data structure unpacking feature: for index , element in enumerate ( iterable ): # ...