Loops That Count: for 🔂
If while is 'keep going until', for is 'do this once for each of these'. It is the loop you will write ninety percent of the time.
for: once for each item
for item in ["rubber chicken", "grog", "map", "sword"]:
print(f"You are carrying: {item}")
You are carrying: rubber chicken
You are carrying: grog
You are carrying: map
You are carrying: sword
Read it as English: "for each item in this collection, do the following". The variable
item is created by the loop and takes each value in turn. You do not
declare it, you do not increment it, and you cannot get the counting wrong, because
there is no counting.
Notice what just disappeared. No counter to initialise, no condition to get backwards, no increment to forget, no off-by-one at the end. An entire genus of bug, extinct, because you described what you wanted instead of how to step through it.
range: when you want numbers
for n in range(5):
print(n)
0
1
2
3
4
range(5) gives five numbers starting at 0: 0, 1, 2, 3, 4.
Five numbers, not up to five. This is the same start-included, end-excluded rule as
slicing, and it is consistent throughout the language.
print(list(range(5)))
print(list(range(1, 6)))
print(list(range(0, 20, 5)))
print(list(range(10, 0, -2)))
[0, 1, 2, 3, 4]
[1, 2, 3, 4, 5]
[0, 5, 10, 15]
[10, 8, 6, 4, 2]
| Written | Means |
|---|---|
range(stop) | 0 up to but not including stop |
range(start, stop) | start up to but not including stop |
range(start, stop, step) | as above, jumping by step (negative counts down) |
Looping over text
for letter in "GROG":
print(letter, end=" ")
print()
word = "banana"
count = 0
for letter in word:
if letter == "a":
count += 1
print(f"{count} letter a's in {word}")
G R O G
3 letter a's in banana
enumerate: when you need the position too
crew = ["Guybrush", "Elaine", "Otis"]
# The clumsy way, which you see beginners write:
for i in range(len(crew)):
print(f"{i}: {crew[i]}")
print("---")
# The Python way:
for i, name in enumerate(crew):
print(f"{i}: {name}")
print("---")
# Humans count from 1:
for position, name in enumerate(crew, start=1):
print(f"{position}. {name}")
0: Guybrush
1: Elaine
2: Otis
---
0: Guybrush
1: Elaine
2: Otis
---
1. Guybrush
2. Elaine
3. Otis
If you ever write for i in range(len(something)), stop. You almost certainly want for item in something, or enumerate if you genuinely need the index. Experienced reviewers spot that pattern instantly.
zip: walking two collections together
names = ["Guybrush", "Elaine", "LeChuck"]
roles = ["pirate", "governor", "ghost"]
for name, role in zip(names, roles):
print(f"{name:10} is a {role}")
Guybrush is a pirate
Elaine is a governor
LeChuck is a ghost
zip stops at the shortest one, which is usually what you want and
occasionally a silent bug. If you need it to complain about mismatched lengths, use
zip(a, b, strict=True), added in Python 3.10.
Accumulating: the pattern behind everything
prices = [4.50, 12.00, 3.25, 8.75]
total = 0
for price in prices:
total += price
print(f"Total: {total:.2f}")
print(f"Average: {total / len(prices):.2f}")
print(f"Built in: {sum(prices):.2f}")
Total: 28.50
Average: 7.12
Built in: 28.50
Start with an empty accumulator, add to it each time round, use it after. That shape
(with a list, a string, a dictionary or a counter) is behind a huge fraction of all
programs. Python also has sum, min, max and
len built in, and you should use them when they fit.
Nested loops
for row in range(1, 4):
for col in range(1, 4):
print(f"{row * col:3}", end="")
print()
1 2 3
2 4 6
3 6 9
The inner loop runs completely for every single step of the outer one: three rows times three columns is nine prints. This is how you handle grids, tables, chessboards and images. It is also where performance goes to die: two nested loops over 1,000 items each is a million steps. Lesson 51 has more to say about that.
FizzBuzz, finally complete
for n in range(1, 21):
if n % 15 == 0:
print("FizzBuzz")
elif n % 3 == 0:
print("Fizz")
elif n % 5 == 0:
print("Buzz")
else:
print(n)
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
That is the whole of the famous interview screening question. It exists because a startling number of applicants cannot write it, and what it really tests is whether you thought about the order of the conditions.
Times table
Print the 7 times table from 7 x 1 to 7 x 12, one line each, neatly aligned.
Reveal solution
for n in range(1, 13):
print(f"7 x {n:2} = {7 * n:3}")
7 x 1 = 7
7 x 2 = 14
7 x 3 = 21
7 x 4 = 28
7 x 5 = 35
7 x 6 = 42
7 x 7 = 49
7 x 8 = 56
7 x 9 = 63
7 x 10 = 70
7 x 11 = 77
7 x 12 = 84Count the vowels
Count how many vowels are in a phrase, and report which ones appeared.
Reveal solution
phrase = "The Secret of Monkey Island"
vowels = "aeiou"
count = 0
found = ""
for letter in phrase.lower():
if letter in vowels:
count += 1
if letter not in found:
found += letter
print(f"{count} vowels")
print(f"which were: {found}")
8 vowels
which were: eoiain works on strings as well as lists, and reads exactly as you would say it out loud. It is one of Python's nicest small features.
Draw a triangle
Print a right-angled triangle of stars, five rows tall, then the same triangle upside down.
Reveal solution
for row in range(1, 6):
print("*" * row)
print()
for row in range(5, 0, -1):
print("*" * row)
*
**
***
****
*****
*****
****
***
**
*No inner loop needed: multiplying a string does the repetition for you. When you can replace a loop with an expression, the code usually gets clearer, not just shorter.