Skip to main content
🔬
PEP 553 · Speedrun · Python 3.7 (2018)

PEP 553 in 90 seconds — breakpoint() and the customisable debug hook

One builtin replaces `import pdb; pdb.set_trace()` — and tests can mute it.

Pre-3.7, dropping into the debugger meant a two-step incantation everyone got wrong: ```python import pdb; pdb.set_trace() ``` Typing `pbd` instead of `pdb`, importing the wrong `pdb` (some shops use `ipdb` or `pudb`), or forgetting the import line entirely — all real footguns. PEP 553 (Python 3.7, 2018) shipped `breakpoint()` as a true builtin.

Two benefits that aren't obvious from "easier to type": 1. **Customisable** — `sys.breakpointhook` is the function `breakpoint()` actually calls. Tests can replace it with a no-op, IDEs can wire it to their own UIs, you can replace it with `ipdb.set_trace` once for the whole process. 2. **Environment-controllable** — set `PYTHONBREAKPOINT=0` and every `breakpoint()` call is silently skipped (PEP 553 honors this). `PYTHONBREAKPOINT=ipdb.set_trace` and the builtin drops into `ipdb` automatically. You can leave `breakpoint()` calls in code committed to CI as long as `PYTHONBREAKPOINT=0` is set in CI's env.

Before — Python ≤ 3.6, two-step pdb dance
def calc(x, y):
    result = x * y
    # Drop into the debugger:
    import pdb; pdb.set_trace()
    return result + 1

# Or for ipdb (richer prompt):
def calc2(x, y):
    import ipdb; ipdb.set_trace()
    return x * y

# Pain points:
# - Easy typo: `import pbd`
# - Tests run this for real unless you remember to strip it
# - No env-var way to disable globally
After — Python 3.7+, breakpoint() + sys.breakpointhook
def calc(x, y):
    result = x * y
    breakpoint(label="after_mul", value=result)  # args passed to hook
    return result + 1

# In tests, mute every breakpoint() in one place:
import sys
sys.breakpointhook = lambda *a, **kw: None  # no-op

# Or globally via env (no code change):
#   PYTHONBREAKPOINT=0 python prog.py     # skip every breakpoint
#   PYTHONBREAKPOINT=ipdb.set_trace ...   # use ipdb instead of pdb

🎯 Predict the output

What does this print? `sys.breakpointhook` is the seam — replacing it lets you observe what `breakpoint(...)` was called with.

import sys
events = []
sys.breakpointhook = lambda *a, **kw: events.append((a, kw))

def calc(x, y):
    result = x * y
    breakpoint(label="after_mul", value=result)
    return result + 1

print(calc(3, 4))
print(events)
Debugging & pdb deep-dive → Senior track

Or speedrun another PEP

PEP 572Assignment as an expression — 3 lines become 1.
PEP 572 in 90 seconds — the walrus that ate your for-loop
PEP 484Python stays dynamic — but you can opt into typing.
PEP 484 in 90 seconds — type hints, without TypeScript-envy
PEP 622Switch statements are a 5% feature. Pattern matching is the 95%.
PEP 622 in 90 seconds — match / case isn't just a switch