← Back to Course Index
Lesson 20 of 20

Debugging

~20 min · breakpoint(), pdb, traceback, warnings, profiling

Ref
Primary Source
Official Docs — pdb — The Python Debugger

The built-in interactive debugger. Also essential: Real Python — Python Debugging with pdb.

1 — Debugging Map: JS → Python

JavaScriptPython
console.log(x)print(x) or print(f"{x=}")
debugger;breakpoint()
Browser DevToolspdb (terminal) / VS Code debugger (GUI)
console.trace()import traceback; traceback.print_stack()
try/catch + rethrowraise (bare re-raise)
console.time()time.perf_counter() / cProfile

2 — print debugging done right

# Basic
print(x)

# f-string debug — shows variable name AND value (Python 3.8+)
x = 42
print(f"{x=}")         # x=42
print(f"{x+1=}")       # x+1=43

user = {"name": "Alice"}
print(f"{user=}")      # user={'name': 'Alice'}

# repr for unambiguous output
print(repr(name))      # shows quotes: 'Alice' vs Alice

# pprint — pretty-print nested structures
from pprint import pprint
pprint(complex_dict, indent=2, width=80)

# Rich inspect — best debug print in Python
from rich import inspect
inspect(obj, methods=True)

3 — breakpoint() — The Debugger

breakpoint() is exactly like the debugger; statement in JS — it drops you into an interactive session at that line.

def process_order(order: dict) -> float:
    subtotal = sum(item["price"] for item in order["items"])
    breakpoint()   # ← execution pauses here, opens pdb
    tax = subtotal * 0.1
    return subtotal + tax
# pdb commands (in the interactive prompt):
(Pdb) l             # l[ist] — show source code around current line
(Pdb) n             # n[ext] — execute next line (step over)
(Pdb) s             # s[tep] — step into function call
(Pdb) c             # c[ontinue] — continue to next breakpoint
(Pdb) q             # q[uit] — exit debugger

(Pdb) p subtotal    # p[rint] variable
(Pdb) pp order      # pp — pretty-print
(Pdb) order         # just typing a name evaluates it
(Pdb) order["items"][0]  # expressions work too!

(Pdb) b 42          # b[reak] — set breakpoint at line 42
(Pdb) w             # w[here] — show call stack (like a traceback)
(Pdb) u             # u[p] — go up one stack frame
(Pdb) d             # d[own] — go down one stack frame
💡 PYTHONBREAKPOINT env var

Set PYTHONBREAKPOINT=0 to disable all breakpoint() calls globally — useful to ensure you didn't leave one in production. Or set PYTHONBREAKPOINT=ipdb.set_trace to use the superior ipdb debugger automatically.

4 — Reading Tracebacks

Python tracebacks read bottom-up — the error is at the bottom, the call chain above it.

Traceback (most recent call last):     ← start reading here
  File "app.py", line 42, in main      ← outer call
    process_user(user_data)
  File "app.py", line 18, in process_user  ← inner call
    result = calculate(user["age"])
  File "app.py", line 7, in calculate  ← where it actually failed
    return 100 / value
ZeroDivisionError: division by zero    ← THE ERROR (read this first!)

# Strategy:
# 1. Read the last line first — that's the error type and message
# 2. Scroll to the last "File" entry — that's where in YOUR code it failed
# 3. Read the chain upward to understand why it was called

Exception chaining

try:
    connect_to_db()
except ConnectionError as e:
    raise RuntimeError("App startup failed") from e
    # Traceback shows both errors:
    # ConnectionError: refused
    # During handling of the above, another exception occurred:
    # RuntimeError: App startup failed

5 — VS Code Debugger (GUI)

You can use VS Code's GUI debugger with Python — set breakpoints by clicking in the gutter, just like browser DevTools.

# .vscode/launch.json — configure the Python debugger
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python: Current File",
      "type": "python",
      "request": "launch",
      "program": "${file}",
      "console": "integratedTerminal",
      "env": {
        "PYTHONBREAKPOINT": "0"  // disable programmatic breakpoints in GUI mode
      }
    },
    {
      "name": "Python: Module",
      "type": "python",
      "request": "launch",
      "module": "mypackage.main"
    }
  ]
}

6 — warnings module

import warnings

# Issue a warning (not an exception — doesn't stop execution)
def old_function():
    warnings.warn(
        "old_function() is deprecated, use new_function() instead",
        DeprecationWarning,
        stacklevel=2   # points warning to CALLER, not this function
    )

# Control warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
warnings.filterwarnings("error")  # turn all warnings into exceptions

# In tests — assert a warning is issued
import pytest
with pytest.warns(DeprecationWarning, match="deprecated"):
    old_function()

7 — Profiling — Finding Bottlenecks

# Quick timing
import time
start = time.perf_counter()
result = slow_function()
print(f"Took {time.perf_counter() - start:.3f}s")

# cProfile — full call-level profiling
import cProfile
cProfile.run("my_function()")

# Line-level profiling (pip install line_profiler)
@profile   # decorator added by line_profiler
def slow():
    total = 0
    for i in range(1_000_000):   # ← line_profiler shows time per line
        total += i**2
    return total
# kernprof -l -v script.py

# Memory profiling (pip install memory_profiler)
@profile
def memory_heavy():
    data = [x**2 for x in range(1_000_000)]
    return data
# python -m memory_profiler script.py

🧠 Quiz

1. print(f"{x=}") outputs:

2. In pdb, which command steps into a function call?

3. In a Python traceback, where is the actual error?

0/3

Questions answered correctly.

🏆

All 20 lessons complete!

You now have a comprehensive Python foundation as a JavaScript developer.
The final step: build a real project.