Why this matters
Most loops in real programs do one thing to every item in a list. They add up the prices in a cart, print each name in a team, or check each score in a class. In all of these cases the position of the item does not matter. The loop only needs the items, one after another.
Python has a loop built for this case. It is the for loop in its simplest form, for item in items:. It hands you each item in turn, so you never write a counter such as i or items[i]. This removes a whole class of bugs, such as stopping one step too early or reading past the end of a list.
The index loop is still needed in some cases. This lesson shows both styles and explains how to choose between them. It does not cover loops that stop on a value, which come in the next lesson.
If you know Java, this is the same idea as its for-each loop. Python uses the same for word for every kind of collection, so there is no separate syntax for lists, strings or dicts.
The idea: visit every element
A list is an ordered row of values. A string is a row of characters. A dict stores pairs of a key and a value. Python calls anything that can hand out its items one at a time an iterable. Lists, tuples, strings, sets, dicts and range objects are all iterables, so a for loop can read from each of them.
Here is the old way, adding up the numbers in a list with an index:
scores = [72, 85, 90, 64, 88]
total = 0
for i in range(len(scores)):
total += scores[i]
print(f"sum = {total}")The output is:
sum = 399The loop counter i goes from 0 up to, but not including, len(scores), which is 5. On each pass scores[i] is one item.
Here is the same sum with a plain for loop:
scores = [72, 85, 90, 64, 88]
total = 0
for s in scores:
total += s
print(f"sum = {total}")The output is the same, sum = 399.
Read the header for s in scores: as "for each s in scores". Python gives s the first item, 72, and runs the indented body. Then it gives s 85, and so on, until the items run out. The indented lines are the body of the loop. Python decides where the body ends by its indentation, which is four spaces here.
The header has three parts: the name you choose for the item, the word in, and the collection to read from. You do not declare a type for s. The name can hold any value, and it takes the type of whatever item it is given. Pick a name that says what the item is. For a list of scores, score reads better than s.
Every kind of iterable works the same way:
names = ["Asha", "Ravi", "Meera"]
for name in names:
print(f"Hello, {name}")
ages = {"Asha": 21, "Ravi": 25}
for name in ages:
print(name)
for name, age in ages.items():
print(f"{name} is {age}")
for age in ages.values():
print(age)The output is:
Hello, Asha
Hello, Ravi
Hello, Meera
Asha
Ravi
Asha is 21
Ravi is 25
21
25A loop over a dict with for name in ages: gives the keys only. To get a key and its value together, call .items(), which gives one pair at a time. The header for name, age in ages.items(): splits each pair into two names. .values() gives the values only. A dict keeps its keys in the order they were added, so the output above always comes out in that order.
A string gives one character at a time. for ch in "tea": runs three times, with ch set to "t", "e" and "a".
Worked example 1: sum and largest
The first example uses two loops over the same list. The first loop finds the total. The second finds the largest score. Neither loop needs a position, so both use the plain for style.
scores = [72, 85, 90, 64, 88]
total = 0
for s in scores:
total += s
print(f"sum = {total}")
best = scores[0]
for s in scores:
if s > best:
best = s
print(f"best = {best}")The output is:
sum = 399
best = 90The variable best starts at scores[0], which is the first item, and not at 0. This matters when every value is negative. The next example shows the difference.
values = [-5, -2, -9]
best = 0
for v in values:
if v > best:
best = v
print("starting at 0 gives", best)
best = values[0]
for v in values:
if v > best:
best = v
print("starting at the first item gives", best)The output is:
starting at 0 gives 0
starting at the first item gives -2If best starts at 0, the answer is 0, which is not in the list at all. Starting from the first real item avoids that mistake. Python also has the built-in function max(values), which returns -2 for this list. Use the loop when you want to learn the pattern or when the comparison is more than a simple maximum.
The loop does not change the list. It only reads each value and updates total or best.
Worked example 2: the loop variable is a name, not a slot
Beginners often try to change a list by assigning to the loop variable. This does not work in Python, and the example below shows why.
nums = [1, 2, 3]
for x in nums:
x = x * 10
print("after loop:", nums)
for i in range(len(nums)):
nums[i] = nums[i] * 10
print("after index loop:", nums)The output is:
after loop: [1, 2, 3]
after index loop: [10, 20, 30]The name x points at one item at a time. The line x = x * 10 makes x point at a new number, 10, 20 or 30. The list still holds 1, 2 and 3, because nothing in the list was changed. The index loop writes to nums[i], which is a slot inside the list, so the list changes.
Python numbers cannot be changed in place, so assigning a new value to the name is the only option with them. Lists can be changed in place. Calling a method on the loop variable changes the item the list holds:
rows = [[1, 2], [3, 4]]
for row in rows:
row.append(0)
print(rows)The output is:
[[1, 2, 0], [3, 4, 0]]Each row is the same list object that rows holds, so append changes it in the outer list too. The rule is this: assigning to the name never changes the list, and changing the object the name points at does.
To write new values back into a list, a list comprehension is the usual tool. It builds a new list from the old one in a single line:
nums = [1, 2, 3]
nums = [x * 10 for x in nums]
print(nums)The output is [10, 20, 30].
Worked example 3: when you still need the index
The plain for loop cannot tell you the position of an item. Some tasks need the position, so an index is the right tool. Here are three cases.
The first case is printing a numbered list. The number shown to the user is the position plus one. Python has enumerate, which gives the position and the item together.
The second case is comparing an item with its neighbour. To see whether a value rose from the one before it, the loop must read temps[i] and temps[i - 1] at the same time. A plain for loop only gives one item at a time, so it cannot look backwards.
The third case is two lists that line up by position. The items list and the prices list are parallel lists. The item at position 2 in one list has its price at position 2 in the other. Python has zip, which walks two lists together.
items = ["apple", "banana", "cherry"]
prices = [30, 12, 45]
for number, item in enumerate(items, start=1):
print(f"{number}. {item}")
for item, price in zip(items, prices):
print(f"{item} costs {price}")
temps = [20, 22, 21, 25, 24]
for i in range(1, len(temps)):
if temps[i] > temps[i - 1]:
print(f"rise at index {i}: {temps[i - 1]} to {temps[i]}")The output is:
1. apple
2. banana
3. cherry
apple costs 30
banana costs 12
cherry costs 45
rise at index 1: 20 to 22
rise at index 3: 21 to 25The second loop starts at i = 1, not at 0, because it looks at i - 1. Starting at 0 would read temps[-1], which has a special meaning in Python. The next example shows what happens.
Use this table to choose a style.
| Task needs | Use |
|---|---|
| Every item, no position | for item in items: |
| The position printed or stored | enumerate(items) |
A neighbour, such as i - 1 or i + 1 | range(1, len(items)) with an index |
| Writing a new value into the list | A list comprehension, or items[i] = ... in an index loop |
| Two lists matched by position | zip(a, b) |
Worked example 4: the negative index trap
In Python a negative index counts from the end of the list. temps[-1] is the last item and temps[-2] is the one before it. Java would stop the program when an index is below 0. Python does not, so a loop that starts too early runs without any error.
temps = [20, 22, 21]
for i in range(len(temps)):
print(i, temps[i] - temps[i - 1])The output is:
0 -1
1 2
2 -1When i is 0, temps[i - 1] is temps[-1], which is 21, the last item. The first line compares 20 with 21, and the answer is wrong. No error appears, so the bug is easy to miss. Start the loop at 1 when the code looks back at i - 1, as in worked example 3.
Worked example 5: removing items while you loop
Removing items from a list while a for loop reads the same list gives wrong results. The program below shows the problem, then a fix.
nums = [2, 4, 6, 8, 9]
for x in nums:
if x % 2 == 0:
nums.remove(x)
print("wrong:", nums)
nums = [2, 4, 6, 8, 9]
nums = [x for x in nums if x % 2 != 0]
print("fixed:", nums)The output is:
wrong: [4, 8, 9]
fixed: [9]The loop keeps an internal position. When remove(2) deletes the item at index 0, the 4 moves into index 0. The loop then moves on to index 1, which now holds 6, so the 4 is skipped. The same thing happens to 8 after 6 is removed. The loop ends with two even numbers still in the list.
Python does not raise an error in this case. Java usually throws a ConcurrentModificationException for the same mistake. In Python the wrong answer comes out silently.
The fix is to build a new list with a list comprehension, as in the second half of the program. To change the same list object, use slice assignment, which replaces the whole content of the list:
nums = [2, 4, 6, 8, 9]
nums[:] = [x for x in nums if x % 2 != 0]
print(nums)The output is [9]. The [:] on the left side means "the whole list", so the list object stays the same and its content changes.
Worked example 6: a None value
None is Python's word for "no value". A for loop asks its collection for items. None has no items, so Python stops with an error.
missing = None
total = 0
for n in missing:
total += n
print(total)The output is:
Traceback (most recent call last):
File ".../main.py", line 3, in <module>
for n in missing:
^^^^^^^
TypeError: 'NoneType' object is not iterableThe folder in the File line depends on where you save the file. The last line is the one to read. It says the value's type is NoneType and that it cannot be looped over.
Check the value before the loop when it might be None:
missing = None
total = 0
if missing is not None:
for n in missing:
total += n
print(total)The output is 0. Use is not None to compare with None, not !=.
Common mistakes
Assigning to the loop variable. Writing x = x * 10 inside a loop changes only the name. The list is not changed (worked example 2). Use a list comprehension or an index loop to write new values.
Looping over a dict and expecting values. A for loop over a dict gives the keys. This code prints the names, not the ages:
ages = {"Asha": 21, "Ravi": 25}
for v in ages:
print(v)The output is Asha and then Ravi. Use ages.values() for the values or ages.items() for both.
Removing items from a list inside a loop over that list. No error is raised, but items get skipped (worked example 5).
Forgetting the colon. Every for header ends with a colon. Without it, Python stops before the loop runs:
for x in [1, 2]
print(x)The output is:
File ".../main.py", line 1
for x in [1, 2]
^
SyntaxError: expected ':'Looping over a number. A number is not a collection, so for i in 5: fails. Use for i in range(5): to get the numbers 0 to 4.
TypeError: 'int' object is not iterableNaming a variable sum. The name sum is a built-in function. Writing sum = 0 hides the function for the rest of the file, so a later call fails:
nums = [1, 2, 3]
sum = 0
for n in nums:
sum += n
print(sum([4, 5]))The output is:
Traceback (most recent call last):
File ".../main.py", line 5, in <module>
print(sum([4, 5]))
~~~^^^^^^^^
TypeError: 'int' object is not callableUse a name such as total instead.
Practice
Exercise 1. Given values = [4, -2, 0, -7, 9], count how many values are negative. Use a for loop.
Show the answer
The output is 2. A plain for loop is the right choice here, because the position of each value is not needed.
Exercise 2. Given nums = [3, 7, 1, 7] and target = 7, print the position of the first element equal to target. If there is none, print -1.
Show the answer
The output is 1. The position is needed, so enumerate is used. The break stops the loop at the first match, which is the one the task asks for.
Exercise 3. Given words = ["tea", "kettle", "mug", "teapot"], print each word that has more than four letters, with its position counted from 1.
Show the answer
The output is:
2. kettle
4. teapotExercise 4. Given values = [0, 3, 0, 5], remove every zero and print the list. Do not change the list inside a loop over it.
Show the answer
The output is [3, 5]. The list comprehension builds a new list that holds only the values that are not zero.
Summary
- A
for item in collection:loop visits every item of a list, tuple, string, set, dict orrange. - Looping over a dict gives its keys. Use
.values()or.items()for the values. - The loop variable is a name. Assigning to it does not change the list, but changing the object it points at does.
- Use
enumeratefor a position,zipfor two lists, andrangewith an index for a neighbour. - Negative indexes count from the end, so
temps[i - 1]withiset to 0 reads the last item without an error. - Removing items from a list inside a loop over that list skips items. Build a new list instead.
- A
Nonevalue cannot be looped over, and the loop stops with aTypeError.