This lesson distinguishes the three kinds of error, covers the specific causes of syntax errors and why their locations mislead, and works through runtime errors by symptom — nothing happening, hanging, and exceptions.
Subject: Python · 65 slides · code lesson
Open the interactive version of this deck
Title
Python · Appendix A — Debugging
§A.1-A.2, pp. 193-197
Objectives
Five things, each one you can check yourself at an interpreter prompt.
Think Python, 2nd edition — Allen B. Downey §A.1-A.2, pp. 193-197 — the pages these objectives are drawn from
Warm-up
You have met all three, without them being named together.
Discussion prompt
A program can fail to run at all, stop partway with a message, or run to completion and produce the wrong answer. What is different about diagnosing each?
Hint: Which one gives you the least to work with?
Answer:
The first two hand you a message and a location — imperfect information, and information. The third gives you nothing but a wrong result.
And they are found at different moments: one before the program runs at all, one during, and one only if somebody checks the output.
Which is why the appendix opens by insisting on the distinction: the first step in debugging is to figure out which kind of error you are dealing with, because the techniques differ.
Concept
When you are debugging, you should distinguish among different kinds of errors in order to track them down more quickly.
The first step in debugging is to figure out which kind of error you are dealing with. Although the appendix is organised by error type, some techniques are applicable in more than one situation.
Figure (svg): The three kinds of error with when each is discovered
Think Python, 2nd edition — Allen B. Downey §A.1-A.2, pp. 193-193
Section
Section 1
Concept
The three kinds differ in when they are found, and each of the book's examples is one you have already met.
# syntax: omitting the colon at the end of a def
# -> SyntaxError: invalid syntax
# runtime: an infinite recursion
# -> maximum recursion depth exceeded
# semantic: an expression evaluated in an unexpected order
# -> an incorrect result, and no message| Kind | What is wrong | When it is found |
|---|---|---|
| syntax | the structure is wrong | found before running |
| runtime | something goes wrong while running | found during |
| semantic | it runs and does the wrong thing | found by checking |
The book's syntax example is worth noting for its verdict as much as its content: omitting the colon at the end of a def statement generates the somewhat redundant message SyntaxError: invalid syntax.
Think Python, 2nd edition — Allen B. Downey §A.1-A.2, pp. 193-193
Picture it
Information decreases from left to right.
Figure (svg): Two columns contrasting errors that report themselves with the one that does not
Which is why the appendix gives the third its own long section, and why chapters 11 and 16 kept recommending self-checks.
Worked example
The symptom names the kind before anything else is known.
# 'it won't even start' -> syntax
# 'it stops with a traceback' -> runtime
# 'it runs and the answer is wrong' -> semantic
# 'it runs forever' -> runtime (a hang)| Symptom | The kind | Note |
|---|---|---|
| no execution at all | syntax | found in translation |
| execution then a message | runtime | found during |
| execution then a wrong answer | semantic | found by checking |
Ask whether it ran.
Why: If not, the interpreter rejected it while translating the source code into byte code — a syntax error.
Ask whether it stopped with a message.
Why: That is a runtime error, and the message includes where it occurred and what functions were executing.
Ask whether the answer is wrong.
Why: A program that runs without producing error messages but doesn't do the right thing has a semantic error.
Figure (svg): The state of the program after each line of Worked example classifying an error from its symptom, drawn as a ladder with one rung per traced line
Three questions, answerable before looking at any code. That classification is the first step, and it decides which techniques apply.
Verify: Check the hang against the classification.
Why: A program that runs forever is a runtime error even though it never stops — the appendix treats it under runtime errors, because execution began. An infinite recursion eventually produces a message and an infinite loop may not, which is why the two get separate diagnoses.
Sorting
Ask when it would be discovered.
Sort into buckets
For each situation, which kind of error is it?
Worked example
One idea about what a program should do, wrong three ways.
if x > 0
print('positive')
# syntax: no colon
if x > 0:
print(y)
# runtime: NameError, if y does not exist
if x > 0:
print('negative')
# semantic: runs, and says the wrong thing| Version | What is wrong | The kind |
|---|---|---|
| the missing colon | the structure is wrong | syntax |
| the undefined name | wrong while running | runtime |
| the wrong word | runs correctly, means wrongly | semantic |
Break the structure.
Why: The interpreter cannot translate it, so nothing runs at all.
Break a reference.
Why: The structure is fine, and the program fails when it reaches the line.
Break the meaning.
Why: Everything is legal and the program does exactly what it says, which is not what was meant.
Figure (svg): A flowchart classifying an error by whether the program ran and what it produced
Three errors from one intention. The third is the only one nothing can detect, which is why it needs a different approach entirely.
Verify: Ask which one a computer could find.
Why: The first two, both automatically — the interpreter finds one before running and reports the other during. The third requires knowing what the program was supposed to do, which is information the program does not contain.
Trap
A program produces a wrong answer, and the developer reads the error message carefully.
Start with the message
Why: Which is the right first move for the other two kinds.
There is no message — a semantic error is a program that runs without producing error messages. Waiting for one, or re-reading output for a diagnosis it does not contain, is time spent on the wrong technique.
Identify the kind, then choose the technique.
A message and a location: read them, carefully
Why: For syntax and runtime errors, they are the best evidence available.
No message: compare what it does with what it should
Why: Which is the whole of semantic debugging, and the next lesson's subject.
The appendix's first instruction is exactly this: the first step in debugging is to figure out which kind of error you are dealing with. The techniques are organised by kind because they do not transfer.
Prediction
The program runs and produces an answer.
def average(t):
return sum(t) / len(t) - 1
# the -1 was not meant to be there| Aspect | What is true | Note |
|---|---|---|
| the syntax | correct | it runs |
| no exception | nothing raises | the arithmetic is legal |
| the answer | wrong by one |
Predict first
Which kind of error is this?
Correct: Semantic — it runs without producing error messages and doesn't do the right thing.
Why: Every step is legal, so nothing is reported: the interpreter translated it and the arithmetic succeeded. That silence is the defining feature — a semantic error can only be found by comparing what the program does with what it was supposed to do.
Faded example
Before any technique.
Fill in the blanks
# the first step in debugging is to figure out
# which kind of error you are dealing with
Why: The appendix opens with this instruction because the techniques are organised by kind and do not transfer: reading an error message is the right first move for two of the three, and useless for the one that produces none.
Explain it to yourself
The appendix is organised by it.
Discussion prompt
Explain why knowing which kind of error you have changes what you should do next.
Hint: What evidence does each one give you?
Answer:
Because each kind supplies different evidence. A syntax error gives a location and an unhelpful message; a runtime error gives a location, a kind of exception and a traceback; a semantic error gives nothing but output.
So the first two start from reading, and the third starts from comparing — deciding what the program should have produced and finding where it diverged.
Which is why the appendix says to identify the kind first. Starting with the wrong technique is not merely slower; for a semantic error it means waiting for a message that is never coming.
Section
Section 2
Concept
Syntax errors are usually easy to fix once you figure out what they are. Unfortunately, the error messages are often not helpful.
SyntaxError: invalid syntax
SyntaxError: invalid token
# neither of which is very informative| Part | How useful | Note |
|---|---|---|
| the message | rarely says what is wrong | invalid syntax |
| the location | does say something | a line number |
| what the location means | where Python NOTICED | not necessarily where the error is |
On the other hand, the message does tell you where in the program the problem occurred. Actually, it tells you where Python noticed a problem, which is not necessarily where the error is — sometimes the error is prior to the location of the error message, often on the preceding line.
Think Python, 2nd edition — Allen B. Downey §A.1-A.2, pp. 193-194
Picture it
The reported line may be innocent.
Figure (svg): A panel showing a syntax error reported on the line after the actual mistake
Which is the single most useful thing to know about syntax errors: the line number is a starting point for a search backwards, not an answer.
Worked example
An unclosed bracket is the clearest case.
x = (1 + 2
y = 3
# Python continues with the next line as part
# of the current statement, so the error appears
# almost immediately in the next line| Line | What Python does | Note |
|---|---|---|
| the open bracket | the statement is unfinished | line 1 |
| the next line | read as a continuation | still line 1's statement |
| the report | line 2 | where it stopped making sense |
Note what an open bracket does.
Why: An unclosed opening operator makes Python continue with the next line as part of the current statement.
Note where it breaks.
Why: Generally, an error occurs almost immediately in the next line — because that line makes no sense as a continuation.
Note the consequence.
Why: The reported line is the innocent one, and the mistake is above it.
Figure (svg): The state of the program after each line of Worked example why the location drifts, drawn as a ladder with one rung per traced line
A report one line below the cause. Knowing the mechanism means the search direction is obvious rather than guessed.
Verify: Check what an unterminated string does instead.
Why: It may cause an invalid token error at the end of your program, or it may treat the following part of the program as a string until it comes to the next string — in which case it might not produce an error message at all. So the drift can be much larger than one line, and sometimes there is no message.
Prediction
The error is reported on line 2.
x = (1 + 2
y = 3
# SyntaxError on line 2| Line | What happens | Note |
|---|---|---|
| line 1 | an unclosed bracket | the statement continues |
| line 2 | read as a continuation | and makes no sense |
| the report | line 2 | where Python noticed |
Predict first
Where is the actual error?
Correct: Line 1 — an unclosed opening operator makes Python continue with the next line as part of the current statement.
Why: The message tells you where Python noticed a problem, which is not necessarily where the error is. Here the drift is one line, which the book says is common — and an unterminated string can push it much further, or produce no message at all.
Worked example
Eight specific causes, worth running through.
# 1. a Python keyword used as a variable name
# 2. a missing colon at the end of a compound header
# 3. unmatched or curly quotation marks
# 4. an unterminated triple-quoted string
# 5. an unclosed (, { or [
# 6. the classic = instead of == in a conditional
# 7. mixed spaces and tabs in the indentation
# 8. non-ASCII characters pasted from elsewhere| Group | What they have in common | Note |
|---|---|---|
| the first six | structural | each with a characteristic symptom |
| the seventh | invisible | which is why repr and a good editor help |
| the eighth | pasted text | usually from a web page |
Run through the list rather than staring.
Why: Syntax errors are usually easy to fix once you figure out what they are, and the list is short enough to check.
Note the two invisible ones.
Why: Mixed spaces and tabs, and non-ASCII characters pasted from a web page — neither is visible in most editors.
Note the recommendation.
Why: The best way to avoid the indentation problem is to use a text editor that knows about Python and generates consistent indentation.
Figure (svg): The eight common causes of syntax errors grouped by how visible each is
Eight causes, of which two produce no visible difference at all. Checking the list is faster than reading the line repeatedly.
Verify: Check the curly quotes warning against where code comes from.
Why: Pasting from a web page, a document or a chat message is exactly where curly quotes and non-ASCII characters come from — two of the eight causes share one origin. Retyping a pasted line is often faster than inspecting it, since the difference is invisible.
Trap
A syntax error names line 40, and the developer reads line 40 repeatedly finding nothing wrong with it.
Look where the error says
Why: Which is the only location available.
The message tells you where Python noticed a problem, which is not necessarily where the error is. Line 40 may be perfectly correct, and re-reading it can go on for a long time.
Search backwards from the reported line.
Check the preceding line first
Why: Sometimes the error is prior to the location, often on the preceding line.
And look for an unclosed bracket or quote above
Why: Which is the usual mechanism for the drift.
There is also a stronger clue: if you are building the program incrementally, you should have a good idea about where the error is — it will be in the last line you added.
Error analysis
Mark each and name the cause.
Annotate
Running the checklist is faster than staring, because two of the eight causes cannot be seen at all.
Discrimination
Some causes cannot be seen.
Sort into buckets
For each cause of a syntax error, is it visible in the source?
Explain it
A common and frustrating situation.
Discussion prompt
A classmate has read the reported line twenty times and can see nothing wrong with it. Tell them what to do.
Hint: What does the location actually mean?
Answer:
The line is probably fine. The message tells you where Python noticed a problem, which is not necessarily where the error is — often the error is on the preceding line.
The usual mechanism is an unclosed bracket or quote above: Python continues with the next line as part of the current statement, and the error appears almost immediately in the next line.
So look upward rather than harder, and check the eight-item list — especially the invisible causes, since staring cannot find mixed tabs and spaces or a pasted non-ASCII character however long you look.
Section
Section 3
Concept
If the interpreter says there is an error and you don't see it, that might be because you and the interpreter are not looking at the same code.
# put an obvious and deliberate syntax error
# at the beginning of the program
!!!!!!
# now run it again.
# If the interpreter doesn't find the new error,
# you are not running the new code.| Step | What it tells you | Note |
|---|---|---|
| the deliberate error | unmissable | at the top |
| if it is reported | you are running this file | look elsewhere |
| if it is not | you are running something else | the real problem |
Check your programming environment to make sure that the program you are editing is the one Python is trying to run. This is the best trick in the appendix, because it distinguishes two situations with identical symptoms.
Think Python, 2nd edition — Allen B. Downey §A.1-A.2, pp. 194-195
Picture it
Changes making no difference has two explanations.
Figure (svg): Two columns contrasting a real bug you cannot see with code that is not being run
Which matters because the second is invisible from inside the code: every technique for finding a bug is useless if the bug is not in the file you are reading.
Worked example
When the test says you are not running the new code.
# 1. you edited the file and forgot to save
# 2. you changed the filename and are running the old one
# 3. the development environment is misconfigured
# 4. your module shares a name with a standard module
# 5. you imported it earlier and it was not re-read| Cause | What happens | Note |
|---|---|---|
| saving | the commonest | some environments do it for you |
| the name clash | yours is shadowed | or shadows one |
| the stale import | chapter 14's warning | restart the interpreter |
Check the simplest first.
Why: You edited the file and forgot to save the changes before running it again — some programming environments do this for you, but some don't.
Check the names.
Why: If you are writing a module and using import, make sure you don't give your module the same name as one of the standard Python modules.
Check the import.
Why: If you are using import to read a module, remember that you have to restart the interpreter or use reload to read a modified file — if you import the module again, it doesn't do anything.
Figure (svg): A flowchart using a deliberate error to decide whether the file is being run
Five specific causes, each with a specific fix. The last is chapter 14's warning, and it is the one that catches people in an interactive session.
Verify: Match the symptom to the import case.
Why: A fix that has no effect at all — not a different wrong result, but byte-for-byte the same behaviour — is the signature of an unread file, which lesson 14c named. The deliberate error test confirms it in one run rather than by reasoning.
Prediction
A deliberate error, and no new message.
# added at the top of the file:
!!!!!!
# run it: the original error is reported,
# and nothing about line 1| Observation | What it means | Note |
|---|---|---|
| the deliberate error | unmissable | if this file were read |
| not reported | the file is not being read | |
| the conclusion | you are running something else |
Predict first
What does this tell you?
Correct: You are not running the code you are editing — if the interpreter doesn't find the new error, the file being run is not this one.
Why: That is the whole point of the test: it distinguishes a bug you cannot see from a file that is not being read, which have identical symptoms. The five likely culprits are an unsaved edit, a renamed file, a misconfigured environment, a module name clash, and a stale import.
Worked example
When nothing else works.
# start again with a new program like 'Hello, World!'
# and make sure you can get a known program to run.
# Then gradually add the pieces of the original
# program to the new one.| Step | What it establishes | Note |
|---|---|---|
| a known-good program | establishes a baseline | does anything run? |
| adding pieces | one at a time | until it breaks |
| the breaking piece | the problem | isolated |
Establish that anything runs.
Why: Get a known program to run, which confirms the environment works at all.
Add the original's pieces gradually.
Why: Each addition either keeps working or breaks, and the one that breaks is the problem.
Note what this is.
Why: Bisection by construction — the same idea as lesson 11c's scale down the input, applied to the program rather than to the data.
Figure (svg): The state of the program after each line of Worked example the last resort, drawn as a ladder with one rung per traced line
A method that works when understanding has failed, because it needs no hypothesis — only the ability to tell whether the program runs.
Verify: Ask what makes this a last resort.
Why: It discards the work of getting the program written and rebuilds it, which is expensive — and it is guaranteed to locate the problem, which nothing else on the list is. That trade is why it comes last rather than first.
Trap
A developer edits a file, runs it, sees no change, and concludes the fix was wrong.
Trust that the edit took effect
Why: The change is visible on the screen.
Some programming environments save for you and some don't — so what is on screen and what is on disk can differ. The fix may have been perfectly correct and never executed.
Test it rather than assuming it.
Put an obvious and deliberate syntax error at the beginning
Why: And run it again.
If the interpreter doesn't find it, you are not running the new code
Why: Which redirects the whole investigation.
This takes seconds and rules out a category of imaginary bug. It is worth doing early rather than after an hour, because the two situations are indistinguishable from the code itself.
Sorting
Five reasons the code you run may not be the code you edit.
Sort into buckets
For each situation, which is the likely cause?
Faded example
Put something unmissable where it will be read first.
Fill in the blanks
# add an obvious and deliberate syntax error
# at the beginning of the program, then run it again
Why: It goes at the beginning so that it is reached before anything else — if the interpreter doesn't find the new error, you are not running the new code. Putting it further down would risk the original error being reported first, which would leave the question unanswered.
Real world
A category of problem that looks like a different problem.
Discussion prompt
Think of a time something appeared broken and the real issue was that you were looking at the wrong thing entirely. How long did it take to notice?
Hint: The wrong file, the wrong copy, the wrong window.
Answer:
Editing one copy of a document while reading another, changing a setting in the wrong profile, testing against a stale version — and the delay is usually long, because every observation is consistent with the change simply not working.
What makes it slow is that the evidence never contradicts the wrong hypothesis. Nothing about the code says you are not reading me.
Which is why a cheap test that distinguishes them is worth so much. The deliberate syntax error takes seconds and settles a question that could otherwise absorb an hour of looking at correct code.
Section
Section 4
Concept
Once your program is syntactically correct, Python can read it and at least start running it. What could possibly go wrong?
The first may be intentional if you only plan to import this module to supply classes and functions — which is exactly what the __name__ guard from chapter 14 arranges.
Think Python, 2nd edition — Allen B. Downey §A.1-A.2, pp. 195-195
Picture it
Two possibilities, and a test for each.
Figure (svg): A flowchart distinguishing an infinite loop from an infinite recursion
Which is why waiting is a diagnostic step: the message that does or does not arrive distinguishes the two.
Worked example
Two prints, and the question is answered.
print('entering the loop')
while ...:
...
print('exiting the loop')
# first message and not the second:
# you've got an infinite loop| What you see | What it means | Note |
|---|---|---|
| neither message | the loop is never reached | a flow problem |
| the first only | an infinite loop | confirmed |
| both | the loop is not the problem | look elsewhere |
Bracket the suspect loop.
Why: Add a print immediately before that says entering the loop and another immediately after that says exiting the loop.
Run it and read which appeared.
Why: If you get the first message and not the second, you've got an infinite loop.
Note the third outcome.
Why: Neither message means the loop is never reached at all, which is a flow-of-execution problem rather than a loop problem.
Figure (svg): A panel showing the three possible outcomes of bracketing a loop with prints
Three possible outcomes from two prints, and each points somewhere different. That is what makes it a diagnosis rather than a guess.
Verify: Ask what to do once the loop is confirmed.
Why: Print the values of the variables in the condition and the value of the condition itself at the end of each pass — so you can see the last time through, where the condition should have been False, and why the variables were not being updated correctly.
Prediction
Two prints bracket the loop.
print('entering the loop')
while x > 0:
...
print('exiting the loop')| When it runs | Note | |
|---|---|---|
| the first print | before the loop | always runs if reached |
| the second | after the loop | only if it ends |
| the diagnosis | which appeared |
Predict first
What pattern of output indicates an infinite loop?
Correct: The first message and not the second — the loop was entered and never exited.
Why: Neither message would mean the loop is never reached, which is a flow-of-execution problem rather than a loop problem; both would mean the loop finished and is not the cause. Two prints distinguish three situations, which is what makes the technique worth the two lines.
Worked example
Look for the base case, then check it is reached.
def f(n):
print(n) # print the parameters
...
return f(n - 1)
# if the parameters are not moving toward
# the base case, you will get some ideas about why not| Question | How to answer it | Note |
|---|---|---|
| is there a base case? | a condition that returns without recursing | first question |
| is it reached? | print the parameters | second question |
| the values | should approach the base case |
Check that a base case exists.
Why: There should be some condition that causes the function to return without making a recursive invocation — if not, you need to rethink the algorithm and identify one.
Check that it is reached.
Why: If there is a base case but the program doesn't seem to be reaching it, add a print at the beginning of the function that prints the parameters.
Read the values.
Why: If the parameters are not moving toward the base case, you will get some ideas about why not.
Figure (svg): The state of the program after each line of Worked example an infinite recursion, drawn as a ladder with one rung per traced line
Two questions in order, and the print answers the second. This is chapter 5's base-case requirement turned into a diagnostic.
Verify: Ask what the printed values look like in each failure.
Why: A missing base case shows parameters marching steadily past where they should have stopped; a base case that is never reached shows parameters that stop changing, or change in the wrong direction. The two look quite different, which is why printing them distinguishes the causes.
Trap
A file of functions and classes is run, produces no output, and is assumed to be stuck.
Read no output as no progress
Why: Which is what a hang looks like.
The program may have finished immediately: this is most common when your file consists of functions and classes but does not actually invoke a function to start execution. Nothing is stuck, because nothing was asked to happen.
Check whether it finished or is still running.
A program that returns to the prompt has finished
Why: However little it printed.
Then make sure there is a function call
Why: And that the flow of execution reaches it.
The appendix notes this may be intentional if you only plan to import the module to supply classes and functions — which is exactly what the __name__ guard arranges, and it means the same file behaves differently depending on how it is used.
Discrimination
Match the symptom to the section.
Sort into buckets
For each symptom, which is the likely cause?
Faded example
Bracket it and see which message appears.
Fill in the blanks
print('entering the loop')
while x > 0:
...
print('exiting the loop')
Why: If you get the first message and not the second, you've got an infinite loop. The pair distinguishes three cases: neither means the loop is never reached, the first only means it never ends, and both mean it is not the problem.
Socratic
Both run forever.
Discussion prompt
An infinite recursion eventually produces a message and an infinite loop usually does not. Why the difference?
Hint: What accumulates in each?
Answer:
A recursion adds a frame to the stack on every call, so something is accumulating — and eventually there is no room, which produces the maximum recursion depth exceeded error.
A loop reuses the same frame. Nothing accumulates, so nothing runs out, and the program can continue indefinitely without anything to report.
Which makes waiting a genuine diagnostic step: the message that arrives or does not tells you which you have, before any code is read. That is why the appendix's first instruction for a hang is to see whether the error appears.
Section
Section 5
Concept
If something goes wrong during runtime, Python prints a message that includes the name of the exception, the line where the problem occurred, and a traceback — which traces the sequence of function calls that got you to where you are.
Each name narrows the search before you read a line of code, which is why the first step is always to read which exception it is rather than only where it happened.
Think Python, 2nd edition — Allen B. Downey §A.1-A.2, pp. 196-197
Picture it
Five names, five different investigations.
Figure (svg): Five common exceptions with the question each one raises
And one of them has a special case worth memorising: an AttributeError mentioning NoneType is about the object rather than the attribute.
Worked example
The most useful entry in the whole catalogue.
AttributeError: 'NoneType' object has no attribute 'sort'
# 'If an AttributeError indicates that an object has
# NoneType, that means that it is None. So the problem
# is not the attribute name, but the object.'| Part | What it means | Note |
|---|---|---|
| the attribute name | not the problem | it may be spelled perfectly |
| the object | is None | which is the problem |
| the cause | somewhere earlier | where the None came from |
Read the type in the message.
Why: NoneType means the object is None, so no attribute of any name would work.
Redirect the investigation.
Why: The problem is not the attribute name, but the object — so checking the spelling is wasted effort.
Find where the None came from.
Why: You forgot to return a value from a function — if you get to the end of a function without hitting a return statement, it returns None. Or you used the result from a list method, like sort, that returns None.
Figure (svg): A panel decoding an AttributeError that names NoneType
An error about an attribute that is really about an object, with two named causes. Recognising it redirects the whole search.
Verify: Match the two causes to earlier lessons.
Why: Both have appeared: lesson 6a's void function returning None when you assign its result, and lesson 10b's t = t.sort() disaster. So this entry is a catalogue of two traps the course already demonstrated, which is why the message is recognisable rather than cryptic.
Prediction
An AttributeError naming NoneType.
AttributeError: 'NoneType' object has no attribute 'sort'| Part | What it says | Note |
|---|---|---|
| NoneType | the object is None | the real problem |
| 'sort' | the attribute name | irrelevant here |
| where to look | where the None came from | earlier |
Predict first
What is the problem?
Correct: The object is None — so the problem is not the attribute name, but the object.
Why: The two usual causes are forgetting to return a value from a function, which returns None by default, and using the result of a list method like sort that returns None. Both have appeared earlier in the course, which is why the message is recognisable — and checking the spelling of sort would waste the whole investigation.
Worked example
Three quite different causes under one name.
# 1. a value used improperly
# e.g. indexing with something other than an integer
# 2. a format string mismatch
# the count or the conversion is wrong
# 3. the wrong number of arguments
# for methods: check the first parameter is self| Cause | What it looks like | Note |
|---|---|---|
| improper use | a value where its type does not fit | indexing with a string |
| format mismatch | lesson 14a's three messages | count or type |
| argument count | lesson 17a's subject | self counts |
Read the rest of the message.
Why: TypeError covers several situations, and the text after the name distinguishes them.
Check the method case specifically.
Why: For methods, look at the method definition and check that the first parameter is self — then look at the invocation and make sure you are invoking it on an object with the right type.
Note that all three are familiar.
Why: Each has appeared: improper indexing in chapter 8, format mismatches in lesson 14a, and the argument count in lesson 17a.
Figure (svg): The state of the program after each line of Worked example the TypeError catalogue, drawn as a ladder with one rung per traced line
One exception name covering three causes, distinguished by the message text. Reading past the name is what identifies which.
Verify: Check the method advice against lesson 17a.
Why: The reminder to check that the first parameter is self is exactly the takes 2 positional arguments but 3 were given trap — the subject counts as an argument. So the appendix's advice is a compressed version of a lesson already learned, which makes it recognisable rather than new.
Trap
A traceback appears and the developer goes straight to the line number.
Find where it happened
Why: Which is the concrete information.
The exception name narrows the search far more than the location does — a KeyError and a TypeError on the same line call for completely different investigations, and the name is free to read.
Read the name, then the location, then the traceback.
The name gives the category
Why: NameError, TypeError, KeyError, AttributeError, IndexError.
The traceback gives the route
Why: It traces the sequence of function calls that got you to where you are.
And for AttributeError, read the type as well: NoneType means the object is the problem rather than the attribute, which sends the search somewhere else entirely.
Sorting
Five names, five categories.
Sort into buckets
For each mistake, which exception does it produce?
Faded example
When an AttributeError names something you expected to exist.
Fill in the blanks
# AttributeError: check the spelling, or use
print(vars(obj))
Why: You can use the built-in function vars to list the attributes that do exist, which usually makes a misspelling obvious immediately — lesson 15b's diagnostic. It answers a different question from the error: the message says what is missing, and vars says what is there instead.
Explain it
An AttributeError on a correctly spelled name.
Discussion prompt
A classmate has checked the spelling three times and is still getting an AttributeError. What should they read in the message?
Hint: What type does it name?
Answer:
The type before the words object has no attribute. If it says NoneType, the object is None — and the problem is not the attribute name but the object, so the spelling was never relevant.
The two usual causes are a function that reached its end without hitting a return statement, which returns None, and using the result of a list method like sort, which also returns None.
And if the type is something else unexpected — a str where a list was intended, say — that is equally informative: the attribute is missing because the object is not the kind of thing they thought it was.
Comparison
Fill the blanks. They differ in when and in what they give you.
Comparison matrix
| Question | Syntax | Runtime | Semantic |
|---|---|---|---|
| When is it found? | while translating to byte code | while the program is running | only if someone checks the output |
| Is there a message? | yes, often unhelpful | yes, with a traceback | no |
| Is there a location? | where Python noticed | where it occurred, plus the calls that led there | none |
| What is the first move? | look at and above the reported line | read the exception name, then the traceback | compare what it does with what it should |
The bottom row is why the appendix insists on identifying the kind first: the three first moves have nothing in common.
Pattern
Six steps, and the first two cost almost nothing.
Step 2 belongs early rather than late. It takes seconds and rules out a whole category of imaginary bug, and the two situations it distinguishes are indistinguishable from the code itself.
Python documentation — Errors and Exceptions Errors and Exceptions
Check
The reported line looks correct.
Check your understanding
What does a syntax error's line number actually tell you?
Answer: A
Why: Sometimes the error is prior to the location of the error message, often on the preceding line — an unclosed bracket makes Python continue with the next line as part of the current statement, so the error appears almost immediately in the line after the real mistake.
Check
The new error is not reported.
Check your understanding
You add an obvious syntax error at the top of the file, run it, and Python does not mention it. What does that mean?
Answer: A
Why: If the interpreter doesn't find the new error, you are not running the new code — which redirects the whole investigation. The five likely culprits are an unsaved edit, a renamed file, a misconfigured environment, a module name clash, and a stale import.
Check
An AttributeError with a particular type.
Check your understanding
What does AttributeError: 'NoneType' object has no attribute 'x' tell you?
Answer: A
Why: If an AttributeError indicates that an object has NoneType, that means it is None — so no attribute of any name would work, and checking the spelling is wasted effort. The two usual causes are a function that returned nothing and the result of a method like sort.
Real world
A report that names where a problem became apparent.
Discussion prompt
Think of a fault reported somewhere other than where it started — a leak, a warning light, a complaint. What made the location misleading?
Hint: Where does the symptom appear?
Answer:
The symptom appears where the effect is visible rather than where the cause is: water surfaces below the pipe, a warning fires when the consequence is detected, a complaint arrives from whoever noticed.
Which is exactly a syntax error's line number: it tells you where Python noticed a problem, which is not necessarily where the error is.
So the useful habit is the same in both settings — treat the reported location as the end of a trail rather than the start, and know which direction the trail runs. For an unclosed bracket, it runs upward.
Commit first
Answer, then rate your confidence. This one saves the most time.
Predict first
Your fix seems to have no effect at all. Before investigating further, what should you do?
Correct: Put an obvious deliberate syntax error at the top of the file and run it, to check you are running the code you are editing.
Why: This is the best trick in the appendix because it separates two situations with identical symptoms: a real bug you cannot see, and a file that is not being executed. If the interpreter doesn't find the new error, you are not running the new code — and no amount of reading, printing or rewriting will help, because the bug is not in the file you are looking at. The five likely culprits are then specific: you edited the file and forgot to save; you changed the filename and are still running the old one; something in the development environment is misconfigured; your module shares a name with one of the standard Python modules; or you imported the module earlier and Python did not re-read it, which chapter 14 warned about. The test takes seconds and belongs early rather than after an hour, precisely because every observation from inside the code is consistent with the wrong hypothesis.
Explain it
Three kinds, and three different first moves.
Discussion prompt
A classmate says their program is broken. Before looking at it, what three questions would tell you how to help?
Hint: The appendix's first instruction.
Answer:
Did it run at all? If not, it is a syntax error — the interpreter rejected it while translating, and the reported line may be below the real mistake.
Did it stop with a message? Then it is a runtime error, and the exception name narrows the search before any code is opened — NameError, TypeError, KeyError, AttributeError or IndexError, each with its own question.
Did it finish and give the wrong answer? That is a semantic error, which produces no message at all — so the techniques are completely different, and that is the next lesson.
Exit ticket
One honest answer. It decides what the next lesson opens with.
Predict first
Which of these is still least solid for you?
Correct: Whichever you picked is the right answer — this one is for you, not for a mark.
Why: The three kinds are worth naming because the appendix's whole structure follows them, and the first move differs completely between them. The syntax error location is the single most useful fact here: it says where Python noticed, not where the error is. The deliberate error test is the one that saves the most time, and it belongs early rather than late. And the exception catalogue is worth reading once so the names become informative — particularly the NoneType case, which sends the search somewhere else entirely.
Connect it up
One page, from memory.
Draw it
Draw a decision tree starting from did it run? and did it stop with a message?, ending at the three kinds of error. Beside the syntax branch, write what the reported line number actually means and two of the eight common causes. Beside the runtime branch, write the five exception names with one question each. Finally write out the deliberate error test in two sentences and list what a negative result would mean.
Recap
Five pages, and a diagnostic procedure rather than a set of facts.
| If you remember one thing | It is this |
|---|---|
| From the three kinds | Identify which before choosing a technique; they do not transfer. |
| From syntax errors | The line number says where Python noticed, not where the error is. |
| From the deliberate error | It separates I cannot see it from I am not running it. |
| From hangs | A recursion accumulates frames and eventually reports; a loop does not. |
| From exceptions | An AttributeError naming NoneType is about the object, not the attribute. |
The next lesson covers the third kind — semantic errors, which produce no message at all — and the strategies for a program that runs perfectly and does the wrong thing.
Think Python, 2nd edition — Allen B. Downey §A.1-A.2, pp. 193-197 — everything on these slides traces back here
Want this taught 1-on-1? Alexander tutors Python — $55/session, free consultation.