Skip to main content
Back to module
Python Fundamentals · Loops and Functions

Reading errors and debugging

Reading30mLesson 3 of 3 in this module

What you'll learn

  • Fix a script using the traceback
  • Read a traceback from the bottom up
  • Match common error types to their usual causes
  • Debug wrong-answer bugs with small known inputs, repr() and assert

Read a Python traceback from the bottom up, recognise common error types and fix bugs methodically.

Why it matters

Every programmer, at every level, writes code that breaks. The difference between a beginner and an experienced developer is not fewer errors, it is how quickly they find the cause. Python is very helpful here: when something goes wrong it prints a traceback that says what failed and exactly where. Learning to read it turns a scary wall of red text into a set of directions.

Anatomy of a traceback

Here is a small script with a bug:

Python
def average(values):
    return sum(values) / len(values)

def report(readings):
    mean = average(readings)
    return f"Mean: {mean:.2f}"

print(report([]))

Running it prints:

Traceback (most recent call last):
  File "sensors.py", line 8, in <module>
    print(report([]))
  File "sensors.py", line 5, in report
    mean = average(readings)
  File "sensors.py", line 2, in average
    return sum(values) / len(values)
ZeroDivisionError: division by zero

Read it from the bottom up:

  1. The last line names the error type (ZeroDivisionError) and a message (division by zero). This is what went wrong.
  2. The frame just above it shows where: file sensors.py, line 2, inside the function average, with the line of code itself.
  3. The frames higher up show how the program got there: line 8 called report, which on line 5 called average. "Most recent call last" means the deepest call is at the bottom.

So the story is: report([]) passed an empty list to average, and len([]) is 0. The fix might be to handle the empty case in average, or to avoid calling it with no data. The traceback tells you where to look; you still decide what the correct behaviour is.

Common error types

ErrorUsual causeExample
SyntaxErrorCode is not valid Python: missing colon, bracket or quoteif x > 3
IndentationErrorInconsistent or missing indentationa block not indented under if
NameErrorA name is misspelled or used before it is definedpirnt("hi")
TypeErrorAn operation got the wrong type of value"5" + 3
ValueErrorRight type, unusable valueint("3.5")
ZeroDivisionErrorDividing by zero10 / 0
IndexErrorList position does not exist[1, 2][5]
KeyErrorDictionary key does not exist{"a": 1}["b"]
AttributeErrorThat type has no such method or attribute[1, 2].upper()

A SyntaxError is special: Python finds it before running anything, so no lines run at all. The caret (^) under the code points at where Python got confused, and the real mistake is often just before it, such as an unclosed bracket on the previous line.

Bugs without errors

The hardest bugs raise no error at all: the program runs and gives the wrong answer. An off-by-one loop or a wrong formula looks perfectly fine to Python. For these you need to check results against values you know:

Python
def average(values):
    total = 0
    for i in range(1, len(values)):   # bug: skips the first item
        total += values[i]
    return total / len(values)

print(average([2, 4, 6]))   # 3.3333333333333335, but should be 4.0

Testing with a tiny input whose answer you can work out by hand exposes the bug immediately.

A debugging method

  1. Reproduce it. Find the smallest input that shows the problem.
  2. Read the last line, then the nearest frame in your own code. Ignore frames inside libraries at first.
  3. Look at the values. Add temporary print calls to see what variables actually hold: print("DEBUG values =", repr(values)). repr shows quotes around strings, so you can tell "5" from 5.
  4. Form one hypothesis and test it. Change one thing, run again. Changing several things at once hides which one mattered.
  5. Check with assert. assert average([2, 4, 6]) == 4 stops with an AssertionError if the result is wrong. The checks in this course's Do tab work exactly this way.
  6. Remove the debug prints when you are done.

Explaining the code line by line to someone else (or to a rubber duck on your desk) is a surprisingly effective step 4. You often spot the bug mid-sentence.

Handling errors you expect

Some errors are not bugs but expected situations, like a sensor sending "n/a" instead of a number. try / except lets you handle them:

Python
def parse_reading(text):
    try:
        return float(text)
    except ValueError:
        return None          # not a number: skip it

print(parse_reading("21.5"))   # 21.5
print(parse_reading("n/a"))    # None

Catch the specific error you expect. A bare except: hides every problem, including typos in your own code, and makes debugging much harder.

Common mistakes

  • Reading only the top of the traceback. The answer is at the bottom.
  • Guessing instead of looking. Print the actual values before changing code.
  • Fixing the symptom. Wrapping everything in try / except makes the error message go away, not the bug.
  • Assuming input types. Text from files and forms is a string until you convert it.

Quick recap

  • Read tracebacks bottom up: error type and message, then the line, then the call chain.
  • Learn the common error types; each points to a typical cause.
  • Wrong-answer bugs need known test cases and assert.
  • Debug one hypothesis at a time, with print(repr(...)) to see real values.
  • Use try / except SpecificError only for situations you expect.

Try it

The Do tab gives you a small sensor script with three bugs. Run the checks, read each failure message, and fix the code until every check passes.

Key terms (6)

traceback
The report Python prints when an error stops a program, showing the error and the chain of calls that led to it.
ValueError
An error raised when a value has the right type but cannot be used, such as int("3.5").
IndexError
An error raised when you ask for a list position that does not exist.
assert
A statement that raises an AssertionError when its condition is false, used to check results.
repr
A built-in that shows a value the way it would be written in code, with quotes around strings.
try
A block that runs code which might fail; a matching except block handles the specific error you expect.

Recap

  • Read a traceback from the bottom up: error type and message, then the line, then the call chain.
  • Each common error type points to a typical cause.
  • Bugs with wrong answers need small inputs with known results and assert.
  • Test one hypothesis at a time, printing repr() to see real values.
  • Use try / except with a specific error only for situations you expect.
Also in this lesson: