Why this matters
Most repetition in real programs is "do this for each thing": each character in a string, each line in a file, each number from 1 to 100, each user in a list. With while you must manage a counter yourself, and forgetting to update it gives an infinite loop. Python's for loop handles all of that: it walks through a collection, one item at a time, and stops by itself at the end. It is the loop you will write most often.
for over a string
for ch in "cat":
print(ch)
c
a
t
Read it as "for each ch in "cat", run the block". On every iteration Python binds the loop variable ch to the next item, here the next one-character string, then runs the body. When there are no more items, the loop ends.
Compare the equivalent while loop:
text = "cat"
i = 0
while i < len(text):
ch = text[i]
print(ch)
i += 1
The for version has no index, no len, no update step and no way to get stuck. When you need each item of something, prefer for.
What for really does
for works with any iterable: an object that can produce its items one at a time. Strings, ranges, lists, dictionaries, files and many more are iterable. Underneath, for asks the object for an iterator (by calling iter() on it) and then repeatedly asks the iterator for the next item (with next()), until the iterator signals that it is exhausted. You will build your own iterators in a later module; for now, "for gives you each item in turn" is the right model.
range: sequences of numbers
To repeat something a number of times, or to loop over numbers, use range:
for i in range(5):
print(i, end=" ")
print()
for i in range(2, 6):
print(i, end=" ")
print()
for i in range(0, 20, 5):
print(i, end=" ")
print()
for i in range(5, 0, -1):
print(i, end=" ")
print()
0 1 2 3 4
2 3 4 5
0 5 10 15
5 4 3 2 1
range follows the same half-open rule as slicing:
| Call | Produces | Rule |
|---|---|---|
range(stop) | 0, 1, ..., stop-1 | starts at 0 |
range(start, stop) | start, ..., stop-1 | stop excluded |
range(start, stop, step) | start, start+step, ... | stops before passing stop |
So range(n) produces exactly n numbers, and range(1, n + 1) gives 1 to n inclusive. A step can be negative to count down; a range that would go the wrong way is simply empty (range(5, 0) produces nothing, so the loop body never runs).
range is lazy
A range object does not store all its numbers. It stores only start, stop and step, and computes each value when asked. range(10**12) takes a few dozen bytes of memory, not terabytes:
r = range(0, 10**12, 3)
print(r)
print(len(r))
print(r[1000])
print(999 in r)
range(0, 1000000000000, 3)
333333333334
3000
True
len, indexing and in are all computed with arithmetic, no looping needed. This is a first taste of lazy evaluation, a theme that returns with generators.
The loop variable
The loop variable is an ordinary name, rebound on each iteration. Two consequences:
- After the loop ends, the name still exists and refers to the last item: after
for i in range(3): pass,iis2. - Assigning to the loop variable inside the body does not affect the iteration; the next iteration simply rebinds it.
for i in range(3):
print(i)
i = 100 # no effect on the next iteration
0
1
2
If you do not need the variable at all, the convention is to call it _:
for _ in range(3):
print("hip hip hooray")
Accumulating with for
The accumulator pattern from the last lesson works the same way, and more safely:
n = int(input())
total = 0
product = 1
for k in range(1, n + 1):
total += k
product *= k
print(f"sum of 1..{n} = {total}")
print(f"{n}! = {product}")
With input 10:
sum of 1..10 = 55
10! = 3628800
Counting is accumulating with += 1 when a condition holds. Combining for, if and an accumulator handles a huge range of problems:
text = input()
digits = 0
spaces = 0
for ch in text:
if ch.isdigit():
digits += 1
elif ch == " ":
spaces += 1
print(f"digits={digits} spaces={spaces}")
With input Room 101 at 9am:
digits=4 spaces=3
Building strings
Strings are immutable, so "building a string" means creating a new one each time and rebinding the name:
word = input()
spaced = ""
for ch in word:
spaced += ch + " "
print(spaced.strip())
With input python, this prints p y t h o n. For a handful of pieces this is fine. For thousands, collect pieces in a list and join them at the end (lists come in the collections module), because each += may copy everything built so far.
Nested loops
A loop body can contain another loop. The inner loop runs completely for each iteration of the outer one:
for row in range(1, 4):
line = ""
for col in range(1, 5):
line += f"{row * col:4}"
print(line)
1 2 3 4
2 4 6 8
3 6 9 12
The outer loop ran 3 times; the inner loop ran 4 times per outer iteration, so the inner body ran 3 x 4 = 12 times. This multiplication matters: two nested loops over n items each do n * n steps, and that grows quickly. A nested loop over a million items would run a trillion iterations.
for or while?
- Use for when you are going through the items of something, or a known count of times (
range). - Use while when the loop continues until some condition changes and you cannot easily say how many iterations that takes: waiting for a sentinel, repeating until convergence, the Collatz process.
Common mistakes
- Off-by-one with range.
range(1, 10)stops at 9. To includen, writerange(1, n + 1). - Looping over
leninstead of the range.for i in len(text):fails withTypeError: 'int' object is not iterable. You meantfor i in range(len(text)):, or better,for ch in text:if you do not need the index. (The iteration module showsenumerate, which gives both.) - Forgetting the colon or indentation, as with
if:SyntaxError: expected ':'. - Resetting an accumulator inside the loop. Initialize it once, before the loop.
- Float steps.
range(0, 1, 0.1)fails withTypeError: 'float' object cannot be interpreted as an integer.rangeworks only with integers; loop over integers and divide:for i in range(10): x = i / 10.
Recap
for item in iterable:runs the block once per item, binding the loop variable each time, and stops by itself.range(stop),range(start, stop),range(start, stop, step)produce integers with the stop excluded; ranges are lazy and supportlen, indexing andin.- Accumulators and counters combine with
forandiffor most "process every item" tasks. - Nested loops multiply: the inner body runs (outer count) x (inner count) times.
- Prefer
forfor "each item" and counted repetition; usewhilewhen the end depends on a changing condition.
Going further: try print(list(range(5, 50, 7))) in the REPL to see a range's values all at once, and import sys; print(sys.getsizeof(range(10**9))) to see how little memory a huge range uses.