The built-in interactive debugger. Also essential: Real Python — Python Debugging with pdb.
| JavaScript | Python |
|---|---|
console.log(x) | print(x) or print(f"{x=}") |
debugger; | breakpoint() |
| Browser DevTools | pdb (terminal) / VS Code debugger (GUI) |
console.trace() | import traceback; traceback.print_stack() |
| try/catch + rethrow | raise (bare re-raise) |
console.time() | time.perf_counter() / cProfile |
# 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)
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
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.
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
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
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"
}
]
}
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()
# 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
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?
Questions answered correctly.
You now have a comprehensive Python foundation as a JavaScript developer.
The final step: build a real project.