PyTips 1 - Get loop counter with enumerate()

In PyTips I'll be talking about Python features, standard libraries, and interesting packages found on PyPi. The idea is to have a short write up and accompanying code snippets for all Python things that I found useful and interesting. These will be mostly aimed at beginners, and I hope that you too, my reader, will find them helpful.

Problem

A common idiom while looping over collection is to use helper variable to store index of the element currently worked on. For example, we want to convert IP address from dotted-decimal to the decimal format:

my_ip = '10.16.32.113'
my_ip_octets = my_ip.split('.')

ip_to_dec = 0
for i in range(len(my_ip_octets)):
    ip_to_dec += int(my_ip_octets[i]) * 256**i

print('{0:<20}{1:<20}'.format('Dotted-decimal', 'Decimal'))
print('{0:<20}{1:<20}'.format(my_ip, ip_to_dec))

While this does the job I think it's not very Pythonic. We have to use len() to get size of our collection, and then feed it to range(). It's not even immediately obvious that we are looping over elements of this collection.

Worse still, you can get IndexError if there's a variable with similar name and you feed it to len() by accident.

Solution: Enter enumerate()

Conveniently Python has built-in function called enumerate() that allows us to loop over collection in a more elegant way. We use enumerate() to wrap around iterated collection and in return we get tuple containing counter value and collection element.

ip_to_dec = 0
for i, octet in enumerate(my_ip_octets):
    ip_to_dec += int(octet) * 256**i
    
print('{0:<20}{1:<20}'.format('Dotted-decimal', 'Decimal'))
print('{0:<20}{1:<20}'.format(my_ip, ip_to_dec))

With enumerate() counter is taken care of and it's more obvious what we're looping over. There's also no risk of mixing up objects as we don't even refer to the original collection object inside of our loop.

Enumerate can also take an argument which tells it to start counter from given value. This is especially useful for human readable ordering where we tend to start counting from 1. A toy example showing the use:

todo_list = [
    'Snooze alarm',
    'Reluctantly get up',
    'Check email',
    'Check Twitter',
    'Check Facebook',
    'Have coffee',
]

print('My TODO for today:')
for i, elem in enumerate(todo_list, start=1):
    print('{:>3}: {}'.format(i, elem))

That's it for Python Tip number one. I hope you found this post useful and I hope to see you again.

You can get source code for this post, and an accompanying Jupyter notebook, from my GitHub repository: https://github.com/progala/pytips/tree/master/PyTips_1_enumerate .