Chapters
Python114 chapters

Control flowChapter 46 of 114

For Loops

Walk a sequence, count with range, and loop over dictionaries.

Looping over a sequence

A for loop takes each item in turn. It works on anything iterable — lists, strings, tuples, sets, dictionaries, files, ranges:

Python
for name in ["Ada", "Grace"]:
    print(name)

for letter in "hi":
    print(letter)

Output

Ada
Grace
h
i

There is no index to manage and no length to get wrong.

range

range(n) counts from zero up to but not including n:

Python
for i in range(3):
    print(i)

Output

0
1
2

With two arguments it is start and stop; with three, a step:

Python
print(list(range(2, 6)))
print(list(range(0, 10, 3)))
print(list(range(3, 0, -1)))

Output

[2, 3, 4, 5]
[0, 3, 6, 9]
[3, 2, 1]

range does not build a list. It produces numbers as they are asked for, which is why range(1_000_000) costs nothing until you loop over it.

Python
print(range(5))
print(list(range(5)))

Output

range(0, 5)
[0, 1, 2, 3, 4]

The loop variable after the loop

It survives, holding the last value. Relying on that is a smell, but knowing it explains some confusing bugs:

Python
for i in range(3):
    pass
print(i)

Output

2

Looping over a dictionary

Plain iteration gives keys. items() gives both:

Python
person = {"name": "Ada", "born": 1815}

for key in person:
    print(key)

for key, value in person.items():
    print(key, value)

Output

name
born
name Ada
born 1815

Unpacking in the loop

When the items are pairs, unpack them in the for line:

Python
pairs = [("Ada", 1815), ("Grace", 1906)]
for name, year in pairs:
    print(f"{name}: {year}")

Output

Ada: 1815
Grace: 1906

Nested loops

The inner loop runs fully on every pass of the outer one:

Python
for row in range(1, 3):
    for col in range(1, 4):
        print(row, "x", col, "=", row * col)

Output

1 x 1 = 1
1 x 2 = 2
1 x 3 = 3
2 x 1 = 2
2 x 2 = 4
2 x 3 = 6

for...else

The else runs if the loop finished without a break. It is the tidy way to express "searched everything and found nothing":

Python
names = ["Ada", "Grace"]

for name in names:
    if name == "Alan":
        print("found")
        break
else:
    print("not in the list")

Output

not in the list

Without it you need a flag variable, which is more code doing less.

Do not change what you are looping over

Adding or removing items while iterating shifts positions under the loop. Loop over a copy, or build a new list:

Python
numbers = [1, 2, 3, 4]
kept = [n for n in numbers if n % 2 != 0]
print(kept)

Output

[1, 3]

Test yourself

2 questions

What does range(3, 0, -1) produce?

Show the answer

3, 2, 1 — Start, stop, step. The stop is not included, so it ends at 1.

Why does range(1_000_000) cost almost nothing until you loop over it?

Show the answer

It produces numbers on demand rather than building a list — Printing a range shows range(0, 1000000) rather than its contents, which is the giveaway.

Next chapter

Break and Continue

Leave a loop early, or skip the rest of one pass.