Free Handbook · Runs in your browser

Errors & Debugging

The anatomy of a traceback, the fifteen errors every beginner hits — indexed, each with why the interpreter said it and the fix — then print debugging done properly, logging, breakpoint() and pdb, reading errors from inside libraries, and the "it works on my machine" checklist.

0 / 108 lessons🔥 0 day streak
ShareXLinkedIn

Module 11 · what you'll be able to do

  • Recognise the fifteen most common Python errors on sight and know the fix for each
  • Debug with prints that actually help, then graduate to logging
  • Stop a program at any line with breakpoint() and inspect it
  • Find your own line inside a library traceback
  • Work through the environment checklist when code behaves differently on another machine
01

Anatomy of a traceback

Module 07 taught the bottom-up reading. This is the reference: what each part of a traceback is, including the pieces newer Python versions added — the ^^^^ markers that point at the exact expression, the "Did you mean" suggestions, and the chained "During handling of the above exception" blocks.

textthe parts
Traceback (most recent call last):            ① header — frames follow, outermost first
  File "app.py", line 22, in main            ② a frame: file, line, function
    total = process(rows)                    ③ the source line of that frame
  File "app.py", line 12, in process
    return row["price"] * qty
           ~~~^^^^^^^^^                      ④ 3.11+: exactly which expression failed
KeyError: 'price'                            ⑤ the exception type and message — START HERE

The above exception was the direct cause of the following exception:   ⑥ chaining (raise … from e)

Traceback (most recent call last):
  ...
PricingError: could not price row 4          ⑦ the outer, higher-level error your code raised
  • Read ⑤ first, then find the last frame that is in your file, then look at ③/④ on that frame.
  • A SyntaxError or IndentationError has no frames — it happened before the program ran — and the caret points at (or just after) the problem.
  • "During handling of the above exception, another exception occurred" means your except block itself crashed. Fix the second one first; it is hiding the first.
  • 3.10+ adds Did you mean: 'total'? to NameError and AttributeError. Trust it.
02

The 15 errors every beginner hits

Every error below is one you will meet in your first month. Each card is the same shape: the code that causes it, the traceback, why the interpreter said it, and the fix. Errors already covered in earlier modules are summarised with a link; the rest are here in full. Bookmark this page.

#ErrorIt meansCovered
1SyntaxError: invalid syntaxThe parser could not read the line — missing colon, bracket, quote, or a keyword used as a name01 · below
2IndentationErrorA block is missing or unevenly indented02
3NameError: name … is not definedTypo, used before assignment, or wrong scope01
4TypeError: can only concatenate str…Mixing str and int in +01
5TypeError: … object is not subscriptable / iterable / callableUsed [ ], a loop, or ( ) on something that does not support itbelow
6TypeError: missing/too many positional argumentsCall does not match the def — often a missing self04 · 08
7ValueError: invalid literal for int()The text is not a numberbelow
8IndexError: list index out of rangeOff-by-one03
9KeyErrorDict has no such key03
10AttributeError: 'NoneType' object has no attribute …Something returned None and you kept goingbelow
11UnboundLocalErrorAssigned to a name inside a function, so it became local04
12ModuleNotFoundError / ImportErrorNot installed here, typo, or circular import05
13FileNotFoundErrorWrong working directory or path06
14ZeroDivisionErrorDivided by zero — usually an empty collection in an averagebelow
15RecursionErrorNo base case, or recursion on a long sequence04
Error you will hit

SyntaxError: '(' was never closed

python
total = sum([1, 2, 3]
print(total)
  File "your code", line 1
    total = sum([1, 2, 3]
                ^
SyntaxError: '(' was never closed
Why the interpreter said that

The bracket on line 1 is never closed, so the parser keeps reading into line 2 and gives up. Older Pythons reported this as a confusing "invalid syntax" on the next line; 3.10+ names the exact bracket. The same family: unterminated string literal (a missing quote) and expected ':'.

The fix

Count brackets on the line the error points at. An editor that highlights matching pairs makes this a non-issue.

python
total = sum([1, 2, 3])
print(total)
Error you will hit

TypeError: 'int' object is not subscriptable / not iterable / not callable

python
count = 5
print(count[0])          # not subscriptable
# for x in count: ...   # would be: 'int' object is not iterable
# count()               # would be: 'int' object is not callable
Traceback (most recent call last):
  File "your code", line 2, in <module>
    print(count[0])
TypeError: 'int' object is not subscriptable
Why the interpreter said that

You used square brackets on an int (or looped over one, or called one). The value is not what you thought it was — most often a variable that was overwritten (list = … shadowing the built-in, then list(x) is "not callable"), or a function returning a single value where you expected a list.

The fix

Print type(count) right before the failing line. Then trace back to where it got that type.

python
counts = [5]
print(counts[0])
Error you will hit

ValueError: invalid literal for int() with base 10: '12.5'

python
price = int("12.5")
Traceback (most recent call last):
  File "your code", line 1, in <module>
    price = int("12.5")
ValueError: invalid literal for int() with base 10: '12.5'
Why the interpreter said that

The type is right (a str) but the value cannot be converted — int() parses whole numbers only. Also raised by "".split(",") unpacking into the wrong number of names (not enough values to unpack), by list.remove(x) when x is absent, and by strptime format mismatches.

The fix

float() for decimals; strip whitespace first; validate and skip bad rows rather than crash on them.

python
price = int(float("12.5"))   # 12 — or keep it a float
print(price)
Error you will hit

AttributeError: 'NoneType' object has no attribute 'upper'

python
def find_user(users, name):
    for u in users:
        if u == name:
            return u
    # falls off the end → returns None

user = find_user(["ada", "linus"], "grace")
print(user.upper())
Traceback (most recent call last):
  File "your code", line 8, in <module>
    print(user.upper())
AttributeError: 'NoneType' object has no attribute 'upper'
Why the interpreter said that

The most common AttributeError by far. Something returned None — a function without a return on some path, a dict.get miss, a regex that did not match, list.sort() or append() assigned to a variable — and the crash happens later, when you use it. The error line is rarely where the bug is.

The fix

Find where the None came from (the function's missing return) and decide: raise, return a default, or check for None at the call site.

python
def find_user(users, name):
    for u in users:
        if u == name:
            return u
    return None       # explicit: callers know to check

user = find_user(["ada", "linus"], "grace")
print(user.upper() if user else "no such user")
Error you will hit

ZeroDivisionError: division by zero

python
scores = []
average = sum(scores) / len(scores)
Traceback (most recent call last):
  File "your code", line 2, in <module>
    average = sum(scores) / len(scores)
ZeroDivisionError: division by zero
Why the interpreter said that

The denominator is zero. In real code it is almost never a literal 0 — it is an empty list in an average, a count of zero rows, or a rate over a zero-length interval. The first day with no data crashes the report.

The fix

Guard the empty case explicitly and decide what it should mean (0? None? skip?).

python
scores = []
average = sum(scores) / len(scores) if scores else 0
print(average)
Quick check

AttributeError: 'NoneType' object has no attribute 'strip' on line 40. Where is the bug most likely?

04

breakpoint() and pdb

When prints are not enough, stop the program and look around. breakpoint() anywhere in your code drops you into pdb, the built-in debugger, at that line: you can print any variable, step line by line, and continue. It does not work in this page (there is no terminal to type into), so this lesson is for your machine — but it is worth the five minutes to learn, and VS Code's graphical debugger is the same thing with buttons.

pythonreport.py
def summarise(rows):
    total = 0
    for row in rows:
        breakpoint()            # execution stops here, every iteration
        total += row["amount"]
    return total

print(summarise([{"amount": 5}, {"amount": "7"}]))
textterminal — a pdb session
$ python3 report.py
> report.py(5)summarise()
-> total += row["amount"]
(Pdb) p row                      # print a variable
{'amount': 5}
(Pdb) p total, type(row["amount"])
(0, <class 'int'>)
(Pdb) n                          # next line
(Pdb) c                          # continue to the next breakpoint (next iteration)
> report.py(5)summarise()
(Pdb) p row
{'amount': '7'}                  # ← there it is: a string
(Pdb) q                          # quit
CommandDoes
p expr / pp exprprint (pretty-print) any expression
nnext line (step over calls)
sstep into the function being called
ccontinue until the next breakpoint
llist the source around the current line
wwhere am I — the stack
u / dmove up / down the stack to inspect a caller's variables
qquit
Post-mortem: debug a crash after it happened
python3 -m pdb report.py then c runs the program and drops you into the debugger at the line that raised, with all variables intact. Or in a script: import pdb; pdb.pm() right after an exception in the REPL.
05

Reading errors from inside libraries

Most real tracebacks are forty lines long and thirty of them are inside pandas, requests or a framework. The rule does not change: the error message at the bottom says what; the last frame in your own file says where you caused it. The library frames between them tell you what the library was trying to do with your input — read them once, then ignore them.

texta library traceback, with the frames that matter marked
Traceback (most recent call last):
  File "etl.py", line 44, in <module>
    main()
  File "etl.py", line 31, in main
    df = load_orders("orders.csv")
  File "etl.py", line 18, in load_orders                          ← ★ your last frame: line 18
    return pd.read_csv(path, parse_dates=["created"])
  File ".../site-packages/pandas/io/parsers/readers.py", line 1026, in read_csv
    return _read(filepath_or_buffer, kwds)
  File ".../site-packages/pandas/io/parsers/readers.py", line 620, in _read
    parser = TextFileReader(filepath_or_buffer, **kwds)
  ... 6 more pandas frames ...
  File ".../site-packages/pandas/io/parsers/c_parser_wrapper.py", line 93, in __init__
    self._reader = parsers.TextReader(src, **kwds)
  File "parsers.pyx", line 581, in pandas._libs.parsers.TextReader.__cinit__
ValueError: Missing column provided to 'parse_dates': 'created'   ← ★ what: the CSV has no "created" column

Two lines out of eighteen. Your line 18 passed parse_dates=["created"]; the file does not have that column. Print pd.read_csv(path, nrows=0).columns and look.

  • Search the exact error message in quotes. Library errors are shared by thousands of people; the first result is usually a GitHub issue with the answer.
  • Check the library version: pip show pandas. Half of "this worked yesterday" is an upgrade.
  • Reproduce in three lines in the REPL with the same input. If it still fails, you have a minimal example — the thing every bug report and every AI tutor needs.
  • When the traceback ends inside a .pyx or C extension, the useful frame is the Python one just above it.
06

"It works on my machine"

Code that runs for you and fails for a colleague, in CI, or on the server is almost never a Python bug. It is one of a short list of environment differences. Work through them in order; the answer is usually in the first three.

  1. 1
    Which Python?

    python3 --version and which python3 on both machines. 3.9 versus 3.12 changes syntax (match, X | Y hints) and behaviour.

  2. 2
    Which packages, which versions?

    pip freeze on both and diff them. This is what requirements.txt with pinned versions exists to prevent.

  3. 3
    Which working directory?

    Relative paths resolve against where python was run from. CI runs from the repo root; you ran from src/. Use Path(__file__).parent.

  4. 4
    Which environment variables?

    API keys, DATABASE_URL, PYTHONPATH. Print os.environ.get("X") at startup; missing on the server means None.

  5. 5
    Which OS?

    Case-sensitive filenames on Linux, \ vs /, line endings, the default file encoding (always pass encoding="utf-8"), and timezone of the machine clock.

  6. 6
    Which data?

    Your local test file has 100 clean rows; production has 10 million and one of them has a null. Test with a sample of the real thing.

  7. 7
    Stale state?

    An old .pyc, a cached result, a leftover file from a previous run. Delete __pycache__ and the output directory and run once more.

pythonEdit it. ⌘/Ctrl + Enter runs.
You should see
python : 3 (see sys.version for the full string)
exe    : True
os     : True
cwd    : True
env    : <not set>

On your machine the second, third and fourth lines print the actual executable path, OS and directory — the version is the Python the browser is running.

Finish the Python handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.