Guide to enumerate() in Python - Easy for Loops with Counting

# Guide to enumerate() in Python - Easy for Loops with Counting

### Introduction

Looping with a counter variable/index - a classic in Computer Science! Typically, you'd either explicitly define a counter variable/index, and manually increment it on each loop, or you'd use some sort of syntactic sugar to avoid this process through enhanced for loops:

some_list = ['Looping', 'with', 'counters', 'is', 'a', 'classic!']

# Manual counter incrementation
i = 0
for element in some_list:
print(f'Element Index: {i}, Element: {element}')
i += 1

# Automatic counter incrementation
for i in range(len(some_list)):
print(f'Element Index: {i}, Element: {some_list[i]}')


Both of these snippets result in the same output:

Element Index: 0, Element: Looping
Element Index: 1, Element: with
Element Index: 2, Element: counters
Element Index: 3, Element: is
Element Index: 4, Element: a
Element Index: 5, Element: classic!


Due to how common looping like this is in day-to-day work - the enumerate() function was built into the Python namespace. You can, without any extra dependencies, loop through an iterable in Python, with an automatic counter variable/index with syntax as simple as:

for idx, element in enumerate(some_list):
print(idx, element)


Note: It's common, but not necessary, convention to name the index as idx if no other label is applicable, since id is a reserved keyword. Commonly, based on the iterable you're working with, more meaningful names can be attributed, such as: batch_num, batch in enumerate(...).

This piece of code results in:

0 Looping
1 with
2 counters
3 is
4 a
5 classic!


Let's dive into the function and explore how it works! It's a classic and common one - and in true Python fashion, it simplifies a common, redundant operation down and improves readability of your code.

### The enumerate() Function in Python

The enumerate() function accepts an iterable collection (such as a tuple, list or string), and returns an enumerate object, which consists of a key-set and value-set, where the keys correspond to a counter variable (starting at 0) and the values correspond to the original elements of the iterable collection:

obj = enumerate(some_list)
print(type(obj))
# <class 'enumerate'>


Note: The enumerate object is, itself, iterable! You can use the standard for syntax, unpacking the keys and values of the enumerate object.

Using Python's standard for syntax, we can unpack the keys and values from this object and inspect their types:

for key, value in obj:
print(type(key), type(value))

# <class 'int'> <class 'str'>
# <class 'int'> <class 'str'>
# <class 'int'> <class 'str'>
# <class 'int'> <class 'str'>
# <class 'int'> <class 'str'>
# <class 'int'> <class 'str'>


The data types of the values (elements from the original collection) are retained, so even if you pass custom data types, as long as they're a valid iterable collection - they'll simply be annotated with a counter variable. If you were to collect the object itself into a list, its structure would become very clear:

print(list(obj))
# [(0, 'Looping'), (1, 'with'), (2, 'counters'), (3, 'is'), (4, 'a'), (5, 'classic!')]


It's just a set of tuples with two elements each - a counter variable, starting at 0, and each element of the original iterable mapped to the indices.

You can set an optional start argument, denoting not the starting index in the iterable, but the starting value for the first counter/index that the function will generate. For instance, say we'd like to start at 1 instead of 0:

obj = enumerate(some_list, 1)
print(list(obj))
# [(1, 'Looping'), (2, 'with'), (3, 'counters'), (4, 'is'), (5, 'a'), (6, 'classic!')]


### Loop Through Iterable with enumerate()

Having said all that - looping through an enumerate object looks the same as looping through other iterables. The for loop comes in handy here as you can assign reference variables to the returned tuple values. Additionally, there's no need to reference the object explicitly, as it's very rarely used outside of a single loop so the returned value is typically used directly in the loop itself:

# No need to assign the returned enumerate object to a distinct reference variable
for idx, element in enumerate(some_list):
print(f'{idx}, {element}')


This results in:

## Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

0, Looping
1, with
2, counters
3, is
4, a
5, classic!


If you'd like to read more about f-Strings and formatting output in Python, read our Guide to String Formatting with Python 3's f-Strings!

Annotating each element in an iterable - or rather, incrementing a counter and returning it, while accessing elements of iterables is as easy as that!

It's worth noting that nothing special really happens within the enumerate() function. It really is, functionally equivalent, to the initial loop we wrote, with an explicit counter variable being returned with an element. If you take a look at the note in the official documentation, the result of the function is functionally equivalent to:

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


You can see that the code is quite similar to the first implementation we've defined:

# Original implementation
i = 0
for element in some_list:
print(f'Element Index: {i}, Element: {some_list[i]}')
i += 1

# Or, rewritten as a method that accepts an iterable
def our_enumerate(some_iterable, start=0):
i = start
for element in some_iterable:
yield i, element
i += 1


The key point here is - the yield keyword defines a generator, which is iterable. By yielding back the index and the element itself, we're creating an iterable generator object, which we can then loop over and extract elements (and their indices) from via the for loop.

If you'd like to read more about the usage of the yield keyword here, read our Guide to Understanding Python's "yield" Keyword!

If you were to use the our_enumerate() function instead of the built-in one, we'd have much the same results:

some_list = ['Looping', 'with', 'counters', 'is', 'a', 'classic!']

for idx, element in our_enumerate(some_list):
print(f'{idx}, {element}')

obj = our_enumerate(some_list)
print(f'Object type: {obj}')


This results in:

0, Looping
1, with
2, counters
3, is
4, a
5, classic!
Object type: <generator object our_enumerate at 0x000002750B595F48>


The only difference is that we just have a generic generator object, instead of a nicer class name.

### Conclusion

Ultimately, the enumerate() function is simply syntactic sugar, wrapping an extremely common and straightforward looping implementation.

In this short guide, we've taken a look at the enumerate() function in Python - the built-in convenience method to iterate over a collection and annotate the elements with indices.

Last Updated: January 31st, 2022

Get tutorials, guides, and dev jobs in your inbox.

David LandupAuthor

Entrepreneur, Software and Machine Learning Engineer, with a deep fascination towards the application of Computation and Deep Learning in Life Sciences (Bioinformatics, Drug Discovery, Genomics), Neuroscience (Computational Neuroscience), robotics and BCIs.

Great passion for accessible education and promotion of reason, science, humanism, and progress.

Free
Course

### Graphs in Python - Theory and Implementation

# python# data structures# algorithms# computer science

Graphs are an extremely versatile data structure. More so than most people realize! Graphs can be used to model practically anything, given their nature of...

Details
Project

### Data Visualization in Python: The Collatz Conjecture

# python# matplotlib# data visualization

The Collatz Conjecture is a notorious conjecture in mathematics. A conjecture is a conclusion based on existing evidence - however, a conjecture cannot be proven....

Details