When things go wrong¶
Goal: read an error message calmly, find the line it points at, and understand the most common kinds of error.
Why this matters¶
Every programmer, on every working day, writes code that does not work the first time. The difference between a beginner and an expert is not that the expert makes no mistakes. It is that the expert reads the error message, and the beginner sees a wall of red text and panics. Error messages are not Python shouting at you. They are Python telling you, quite precisely, what went wrong and where. This lesson teaches you to read them.
Three kinds of error¶
Things can go wrong in three different ways, and each one needs a different response.
1. Python cannot read your code¶
A syntax error means the code breaks the grammar rules of Python, the way a sentence with no verb breaks the rules
of English. Syntax is simply the word for those grammar rules. Here the colon after the if is missing:
print("Starting the program")
temperature = 25
if temperature > 20
print("It is warm today.")
print("Finished")
Look at what is not in the output: Starting the program never appeared, even though that line is fine and comes
first. Python reads the whole file before running any of it, and if it cannot understand the file, it runs nothing at all.
The message gives you the file, the line number, a copy of the line, and a little arrow ^ under the spot where
Python got confused. IndentationError, which you met in Module 1, is a kind of syntax error too.
2. Python runs your code, then crashes¶
The second kind happens while the program is running. The code is good grammar, but Python reaches a line it cannot carry out:
print("Starting the program")
sweets = 12
children = 0
print("Sharing the sweets...")
each = sweets / children
print(f"Each child gets {each} sweets.")
print("Finished")
Starting the program
Sharing the sweets...
Traceback (most recent call last):
File "runtime_error.py", line 6, in <module>
each = sweets / children
~~~~~~~^~~~~~~~~~
ZeroDivisionError: division by zero
This time the first two print lines worked. The program ran until line 6, could not divide by zero, and stopped.
Finished never appeared. An error like this is called an exception, because something exceptional happened that
the program was not prepared for. Almost every error you have met in this course was an exception.
3. Python runs your code, and the answer is wrong¶
The third kind is the sneaky one:
first = 8
second = 6
third = 10
average = first + second + third / 3
print(f"The average is {average}")
No error message. No crash. But the average of 8, 6, and 10 cannot be 17, because it is bigger than all three numbers.
This is a logic error: the code is valid and runs fine, but it does not do what you meant. Python divides before
it adds, as you saw in Module 1, so only third was divided by 3. The fix is parentheses: (first + second + third) / 3.
Python cannot warn you about logic errors, because it does not know what you wanted. Only you can catch them, by checking your program's answers against ones you worked out yourself. If you remember one thing from this section, make it this: a program that runs without errors is not the same as a program that is correct.
Reading a traceback¶
The report Python prints when an exception happens is called a traceback, because it traces back through your program to show how it got to the line that failed. Tracebacks get longer when functions are involved, and that is when people start to panic, so let us take one apart:
def average(numbers):
return sum(numbers) / len(numbers)
def print_report(name, scores):
result = average(scores)
print(f"{name}: {result}")
print_report("Ada", [90, 80, 100])
print_report("Grace", [])
print_report("Linus", [70, 75])
Ada: 90.0
Traceback (most recent call last):
File "traceback_demo.py", line 9, in <module>
print_report("Grace", [])
File "traceback_demo.py", line 5, in print_report
result = average(scores)
^^^^^^^^^^^^^^^
File "traceback_demo.py", line 2, in average
return sum(numbers) / len(numbers)
~~~~~~~~~~~~~^~~~~~~~~~~~~~
ZeroDivisionError: division by zero
Read a traceback from the bottom up. The most useful information is at the end:
- The last line is the error itself. It has two parts: the type of error,
ZeroDivisionError, and a message with details,division by zero. Read this first. Often it is all you need. - The lines just above it say where.
File "traceback_demo.py", line 2, in averagemeans line 2 of that file, inside the functionaverage. Below that is a copy of the line, with marks under the exact part that failed: the division. - The lines above that say how the program got there. Line 2 ran because line 5, in
print_report, calledaverage. Line 5 ran because line 9 calledprint_report.<module>means the main part of your file, outside any function.
That is what most recent call last means at the top: the calls are listed in the order they happened, so the newest,
where the crash is, comes last.
Now put it together like a detective. Line 2 divided by zero, so len(numbers) was 0, so the list was empty. Where did
an empty list come from? Follow the trail upwards: line 9 passed [] for Grace. The crash was noticed on line 2, but
it was caused on line 9. That is why the trail matters: the line Python points at is where the problem showed up,
which is not always where the mistake was made.
Notice too that Ada's report printed before the crash, and Linus's never ran. An exception stops the program on the spot.
A tour of the errors you have met¶
You have already seen most of the common exceptions in earlier modules. Here they are in one place. Each tab shows a tiny program that causes the error and the last line of its traceback.
You used a name Python has never heard of. Nearly always a spelling mistake, or a variable used before the line that creates it. Python often suggests the name you meant.
The kind of value is wrong for what you are doing with it, such as adding text to a number. Convert with str(),
int(), or float(), or use an f-string. Calling a function with the wrong number of arguments is a TypeError too.
The kind of value is right, but this particular value is no good. int() accepts strings, but not the string
"twelve". You will see this whenever a user types words where you expected a number.
You asked for a position that is past the end of the list. A list of three items has indexes 0, 1, and 2, so
colors[3] does not exist.
You asked a dictionary for a key it does not have. The message is the missing key. Check the spelling and the
capital letters, or test with in first.
The file is not where Python looked. Check the spelling, and check which folder your terminal is in.
You do not need to memorise these. You need to recognise them, and you will, because you will see each one many more times.
Finding a bug with print()¶
An exception tells you where to look. A logic error gives you nothing, so you have to make the program show you what it is doing. This function should return 20.0, and it returns 10.0:
def average(numbers):
total = 0
for number in numbers:
total = number
return total / len(numbers)
print(average([10, 20, 30]))
You could stare at the code until you see the mistake. A faster and more reliable method is to stop guessing and
look. Add print() calls that show the variables at the moments that matter:
def average(numbers):
total = 0
for number in numbers:
total = number
print(f"DEBUG number={number} total={total}")
print(f"DEBUG after the loop: total={total} len={len(numbers)}")
return total / len(numbers)
print(average([10, 20, 30]))
DEBUG number=10 total=10
DEBUG number=20 total=20
DEBUG number=30 total=30
DEBUG after the loop: total=30 len=3
10.0
Now the bug is visible. After the second number, total should be 30, and it is 20. It is not adding up, it is
just copying each number. Look at the line that changes total, and there it is: total = number should be
total += number.
Finding and fixing mistakes like this is called debugging, and a mistake in a program is called a bug. Three habits make print debugging work well:
- Label every print.
print(total)gives you a bare20and you soon forget what it meant.total=20explains itself. - Mark them so you can find them again. Starting each one with
DEBUGmakes them easy to spot and delete when the bug is fixed. Do delete them. - Predict before you run. Decide what each value should be, then compare. The first value that differs from your prediction is right next to the bug.
The same trick works for exceptions. If a line crashes and you cannot see why, print the variables it uses on the line before it, and run the program again.
Seeing invisible characters
Spaces and newlines at the end of a string are invisible when printed. Put square brackets around the value,
print(f"[{line}]"), and a stray newline or space shows up at once.
Try it¶
Cause each kind of error on purpose, so that you have seen them when nothing was at stake. Take any working program
from an earlier lesson. First delete a closing parenthesis and run it. Put it back, misspell a variable name, and run it.
Put that back, change a + to a -, and run it. For each one, note whether the program started at all, whether Python
gave you a line number, and whether Python noticed anything wrong.
Common mistakes¶
Reading the traceback from the top
The top of a long traceback is the least useful part. Go straight to the last line for what went wrong, then one step up for where.
Not reading the message at all
It is tempting to see red text, go back to the code, and start changing things. Read the last line first, every
time. Python very often names the exact problem, and sometimes even the fix: Did you mean: 'greeting'?
The line Python points at looks fine
Then the mistake was made earlier and only showed up here. Print the variables that the line uses. One of them holds something you did not expect, and the question becomes where that value came from.
Changing several things at once
If you make five changes and the program works, you do not know which one fixed it. If it still fails, you may have added new bugs. Change one thing, run, and look.
I fixed the error and now there is a different error
That is progress. Python stops at the first problem it meets, so fixing one reveals the next. Keep going.
It runs without errors, so it must be right
Python only checks that it can run your code, never that the answer is what you wanted. Test with values where you already know the answer.
Exercises¶
Each of these programs is broken. The comment at the top says what it should do. Copy each one, run it, read the error, fix one thing, and run it again. Every program contains more than one bug, so expect a new error after each fix.
-
Broken shopping. Three bugs. Two of them stop the program from starting at all.
-
Broken grades. Three bugs, all exceptions. For each one, write down the error type before you fix it.
broken_grades.py# Should print the three students with their grades, # then: Best: Grace, and then: Ada will be 37 next year. names = ["Ada", "Grace", "Linus"] grades = {"Ada": 88, "Grace": 95, "Linus": 72} for i in range(4): name = names[i] print(name + " got " + grades[name]) best = max(grades.values()) for name, grade in grades.items(): if grade == best: print(f"Best: {name}") ages = {"ada": 36} print(f"Ada will be {ages['Ada'] + 1} next year.") -
Broken converter. Two bugs, and Python will not help you with either: the program runs without any error. Water freezes at 32 F, which is 0 C. Use that, and the last line given in the comment, to check the output. Add
DEBUGprints if you get stuck.broken_converter.py# Should print a table from 0 to 100 degrees Fahrenheit in steps of 10. # The last line should be: 100 F = 37.8 C def to_celsius(fahrenheit): return fahrenheit - 32 * 5 / 9 for fahrenheit in range(0, 100, 10): celsius = to_celsius(fahrenheit) print(f"{fahrenheit} F = {round(celsius, 1)} C")
Solution 1
prices = {"bread": 2.5, "milk": 1.5, "cheese": 3.5}
total = 0
for item, price in prices.items(): # bug 1: the colon was missing
print(f"{item}: {price}")
total += price
print(f"Total: {total}") # bug 2: ) was missing, bug 3: totl
The errors arrive one at a time: SyntaxError: expected ':' on line 5, then SyntaxError: '(' was never closed
on line 9, and only then, once Python can read the whole file, the program starts and hits
NameError: name 'totl' is not defined. Syntax errors always come first, because nothing runs until they are gone.
Solution 2
names = ["Ada", "Grace", "Linus"]
grades = {"Ada": 88, "Grace": 95, "Linus": 72}
for i in range(len(names)): # bug 1: range(4) ran past the end
name = names[i]
print(name + " got " + str(grades[name])) # bug 2: a number needs str()
best = max(grades.values())
for name, grade in grades.items():
if grade == best:
print(f"Best: {name}")
ages = {"Ada": 36} # bug 3: the key was "ada"
print(f"Ada will be {ages['Ada'] + 1} next year.")
In order: a TypeError from gluing a number onto text, an IndexError because range(4) counts 0, 1, 2, 3 and
the list stops at index 2, and a KeyError because "ada" and "Ada" are different keys. range(len(names))
is better than range(3), because it stays right when the list grows. Simpler still is for name in names:,
which has no index to get wrong.
Solution 3
def to_celsius(fahrenheit):
return (fahrenheit - 32) * 5 / 9 # bug 1: the parentheses were missing
for fahrenheit in range(0, 101, 10): # bug 2: range stops before its end
celsius = to_celsius(fahrenheit)
print(f"{fahrenheit} F = {round(celsius, 1)} C")
Without parentheses, Python works out 32 * 5 / 9 first and subtracts the result from fahrenheit. The second bug
is that range() stops before its end value, so range(0, 100, 10) finishes at 90. Did you notice that the
very first line of the broken output, 0 F = -17.8 C, was correct? It was right by luck. One correct answer
does not prove a program works, which is why you test with several values.
Summary¶
- Syntax errors stop the program before it starts. Exceptions crash it while it runs. Logic errors give a wrong answer with no message at all.
- Read a traceback from the bottom up: the last line says what went wrong, the lines above say where, and the lines above those say how the program got there.
- The line in the traceback is where the problem was noticed. The cause may be earlier.
- When you cannot see the bug, stop guessing and print the variables. Label the prints, and delete them afterwards.
- Fix one thing at a time, and test with values where you know the right answer.
So far, an exception has always meant the end of the program. In the next lesson you will learn to catch exceptions,
so that a user who types abc instead of a number gets a polite second chance instead of a traceback.