Looping through an iterable is the most basic and fundamental ability of any programming language.

Python’s for loop is at a first glance very straightforward, but just like with any other tool it can be used in multiple ways - these become handy when you start taking performance into account.

Here are 6 things to consider when you need to loop over something.

1. Maybe I don’t actually need a for-loop

For loops can be slow. There are many built-in methods optimized for specific tasks. Perhaps a better approach would be to simply use a built-in function. Let’s say you need to sum up a list of numbers.

1
numbers = [5, 10, 15, 20, 25, 30]

You can either loop through the list and sum up the numbers.

1
2
3
result = 0
for n in numbers:
	result += n

Or, use a built-in sum() function.

1
result = sum(numbers)

2. I need both the index and the value

Traditional way is to index each item.

1
2
3
numbers = [5, 10, 15, 20]
for i in range(len(numbers)):
	print(i, numbers[i])

But Python offers a function that lets you access both values at the same time - enumerate(). It returns a tuple, which you can immediately unpack.

Thing I discovered recently is that you can pass another argument into enumarate, start, and define a number from which you’d like to start (as arrays are 0-indexed).

1
2
3
4
5
numbers = [5, 10, 15, 20]
for i, num in enumerate(numbers, start=1):
	print(i, num)
# 1, 5
# 2, 10 ...

3. I need to loop over multiple iterables at once

Using zip() function is very useful here. Achieving this through regular for-loop is very error-prone.

1
2
3
4
5
numbers = [1, 2, 3]
letters = ['a', 'b', 'c']

for i in range(len(numbers)):
	print(numbers[i], letters[i])

Now, if you increase numbers by one, you will get IndexError: list index out of range. Instead, you can use zip() function.

1
2
3
4
for value1, value2 in zip(numbers, letters):
	print(value1, value2)

# automatically stops at the shorter list

Sometimes though, you still need the error message in order to avoid bugs, for that you can pass another argument strict=True (new from Python 3.10), and this will raise a ValueError: zip() argument 2 is shorter than argument 1.

1
2
3
4
5
6
7
numbers = [1, 2, 3, 4] # this list is longer
letters = ['a', 'b', 'c']

for value1, value2 in zip(numbers, letters, strict=True):
	print(value1, value2)

# ValueError: zip() argument 2 is shorter than argument 1*

4. Think Lazy & use a Generator

Let’s say you want to calculate how much time you spend on a specific task.

1
2
3
4
5
tasks = [('reading', 5), ('reading', 20), '...']
minutes_on_task = 0
for task in tasks:
	if task[0] == 'reading':
		minutes_on_task += task[1]

Another useful approach might be to use a generator with a sum() function.

1
2
3
# This is lazy - not yet executed
reading_time = (task[1] for task in tasks if task[0] == 'reading') # generator expression
time_spent = sum(reading_time)

Main difference - a generator is evaluated lazily - nothing happens until we call sum() on it. Thing to be aware of - objects are consumed when using the generator, which means if we were to print time_spent again, the result would be 0.

5. Itertools

Functions creating iterators for efficient looping. Module that brings many functions to improve looping. So far I tried 3 of them and found them really useful.

  • islice()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from itertools import islice

notes = ['row1', 'row2', 'row3', '...', 'rown']

# Let's grab first 5 rows
# You can add additional arguments [start, end, step]
first_five_rows = islice(rows, 5)
for line in first_five_rows:
	print(line)

# Again, iterating second time results to empty list, as it is already consumed
  • pairwise()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from itertools import pairwise

letters = 'ABCDEFG'

# Return successive overlapping pairs
# returns tuple
for pair in pairwise(letters):
	print(pair[0], pair[1])

# A B
# B C
# C D ...
  • takewhile()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from itertool import takewhile
numbers = [2, 4, 6, -2, 3, 1]

# Return numbers until we reach our condition (negative number)
for n in takewhile(lambda x: x >= 0, numbers):
	print(n)

# 2
# 4
# 6