What does returning sys.monitoring.DISABLE from an event callback do?
answer
- The sentinel a callback can return
- Stops paying for a spot you already saw
- Keyed by instruction offset, not by line
- Only local events; restart_events undoes it
basics
~20 sIt retires that one code location for that event and that tool: CPython de-instruments the instruction, so the callback never fires from that spot again and later executions run at almost full speed. sys.monitoring.restart_events re-arms every retired location.
solid answer
~40 s`sys.monitoring.DISABLE` is a sentinel object. Returning it from a callback tells the interpreter never to call that tool back for that event at that exact instruction again, and CPython removes the instrumentation there. Because instrumentation is per code object and per instruction, a line inside a hot loop costs one callback rather than one per iteration — that is what makes line coverage cheap on 3.12 and later. Only the local events can be retired this way: `PY_START`, `PY_RESUME`, `PY_RETURN`, `PY_YIELD`, `CALL`, `LINE`, `INSTRUCTION`, `JUMP`, `BRANCH_LEFT`, `BRANCH_RIGHT` and `STOP_ITERATION`. There is no per-location undo; `sys.monitoring.restart_events()` re-arms everything for every tool at once. Returning `None` leaves the location armed, which is what a counting profiler wants.
code
python · 30 linesimport sys
TOOL_ID = sys.monitoring.COVERAGE_ID
hits = []
def on_line(code, line_number):
hits.append(line_number)
return sys.monitoring.DISABLE
def loop():
total = 0
for i in range(1000):
total += i
return total
sys.monitoring.use_tool_id(TOOL_ID, "line-coverage")
sys.monitoring.register_callback(TOOL_ID, sys.monitoring.events.LINE, on_line)
sys.monitoring.set_local_events(TOOL_ID, loop.__code__, sys.monitoring.events.LINE)
loop()
print("callbacks after 1000 iterations:", len(hits))
sys.monitoring.restart_events()
loop()
print("callbacks after restart_events:", len(hits))
sys.monitoring.set_local_events(
TOOL_ID, loop.__code__, sys.monitoring.events.NO_EVENTS
)
sys.monitoring.free_tool_id(TOOL_ID)go deeper
Recall that a callback can return a special sentinel meaning 'never call me here again', and that this is why modern coverage tooling is cheap. Naming sys.monitoring.DISABLE and its effect is enough at this level.
Explain the mechanics: retirement is keyed to one instruction offset in one code object for one tool, only local events qualify, and restart_events is the only way back. Be able to say why a for header may fire twice.
Demonstrate the judgement call — reachability tools should retire locations, counting and timeline tools must not — and be able to reason about the residual cost of instrumented-but-disabled code left armed in a long-running service.
Own the tradeoff between always-on instrumentation and on-demand diagnostics: what overhead budget the platform accepts, whether a shared restart_events call is safe when several tools coexist, and how sampling policy is agreed across teams.
`sys.monitoring.DISABLE` is a unique sentinel object exposed by the `sys.monitoring` module. Returning it from an event callback is the API's central performance idea, and it is the reason line-level coverage on Python 3.12 and later can run at a fraction of the cost it used to. ### What actually gets retired When a tool subscribes to an event, CPython rewrites the affected code objects so that the relevant bytecode instructions dispatch into the monitoring machinery. The unit of instrumentation is therefore **one instruction offset inside one code object**, not a function and not a source line. When your callback returns `sys.monitoring.DISABLE`, the interpreter de-instruments that exact location for that exact event and that exact tool. The next time control reaches that instruction, there is no dispatch at all: no callback frame, no argument boxing, no return-value check. The location is simply back to being ordinary bytecode for you, while remaining instrumented for any other tool that subscribed to it. That is a fundamentally different economy from "call me every time and I will return early". A flag check inside the callback still pays a full Python call on every execution. `DISABLE` pays for the first execution and nothing thereafter. ```python def on_line(code, line_number): hits.append(line_number) return sys.monitoring.DISABLE ``` A loop body that executes a million times fires that callback once per line, then goes quiet. For a coverage recorder — which only needs to know *whether* a line ran, never how often — this is exactly the right trade. ### Per instruction, not per line A subtle consequence: a single source line can compile to more than one instrumented offset. A `for` header is the common case — the loop setup and the iteration step are different instructions attributed to the same line — so a `LINE` callback that returns `DISABLE` may still see that line twice before it goes quiet. This is not a bug and it is worth being able to explain: the disable state is keyed by instruction offset, and the line number is just what the callback is handed. ### Which events can be retired this way `sys.monitoring` divides its events into **local** and **global-only**. The local events are the ones attached to a specific instruction in a specific code object, and they are exactly the set `sys.monitoring.set_local_events` will accept: `PY_START`, `PY_RESUME`, `PY_RETURN`, `PY_YIELD`, `CALL`, `LINE`, `INSTRUCTION`, `JUMP`, `BRANCH_LEFT`, `BRANCH_RIGHT` (and the older `BRANCH`), and `STOP_ITERATION`. The rest — `RAISE`, `RERAISE`, `PY_UNWIND`, `PY_THROW`, `EXCEPTION_HANDLED`, `C_RETURN`, `C_RAISE` — are global-only. `sys.monitoring.set_local_events` rejects them with `ValueError: invalid local event set`, and per-location retirement is not the tool to reach for with them. If you need to sample exception events, filter in your own callback and accept the dispatch cost. ### Undoing it There is **no per-location undo**. `sys.monitoring.restart_events()` clears the disable state for every location and every tool at once, re-arming everything that is still subscribed. A coverage tool calls it at the start of each measured run; a long-lived diagnostic that wants a fresh sample calls it on an interval. Because it is process-wide across tools, a library should not call it casually — it will re-arm someone else's carefully disabled locations too. Disabling is otherwise sticky for the life of the process. Toggling the event off with `sys.monitoring.set_events(tool_id, sys.monitoring.events.NO_EVENTS)` and back on does not by itself restore a disabled location. ### When not to use it `DISABLE` is wrong whenever you need every occurrence: * A counting profiler that reports call totals. Retire the location and the count stops at one. * A tracer reconstructing an execution order or a timeline. * Anything sampling values rather than reachability — if you disable after the first observation, you never see the value that actually differed. The heuristic is simple: `DISABLE` answers "did this ever run?", never "how often" or "with what". Returning `None` — or anything that is not the sentinel — leaves the location armed, which is the default and the right choice for those cases. ### The residual cost Retiring every location does not return the code object to its pre-instrumentation form; the code stays rewritten and pays a small, constant overhead even with everything disabled. It is close to baseline and far below the un-disabled cost, but "free" overstates it. That distinction matters when someone proposes leaving a fully-disabled coverage tool armed in production.
- How do you re-arm locations that callbacks retired earlier in the same process?`sys.monitoring.restart_events()` clears the disable state for every location and every tool at once. There is no per-location undo, and toggling the event set off and on again does not restore it. A coverage tool calls `restart_events` at the start of each measured run. Because it is process-wide across tools, a library should not call it casually — it re-arms other tools' deliberately retired locations too.
- Does DISABLE retire the whole code object or a single instruction?A single instruction offset. A source line that compiles to more than one instrumented offset — a `for` header is the usual case — can therefore fire the callback twice for the same line number before it goes quiet. The disable state is keyed by offset; the line number is only what the callback is handed.
- Why is returning DISABLE cheaper than checking a flag inside the callback?A flag check still pays a full Python call on every execution: frame setup, argument boxing, return-value handling. DISABLE removes the dispatch itself, so subsequent executions never enter your code at all. The instrumented code object does keep a small residual overhead, so it is close to baseline rather than exactly free.
- When should a tool not return DISABLE?Whenever it needs every occurrence: a profiler counting calls, a tracer reconstructing execution order, or anything sampling values rather than reachability. Retire the location and the count stops at one, and you never see the execution whose value actually differed. DISABLE answers 'did this ever run?', never 'how often' or 'with what'.
A tripwire that cuts itself after the first trigger. You learn that someone walked through the door, you stop paying to watch that door, and only a global reset restrings every wire in the building.
saying these in an interview costs you the question
- Thinks DISABLE turns the event off for the whole program
- Returns False or None expecting the location to be retired
- Believes retirement is per source line rather than per instruction
- Assumes retired locations re-arm by themselves over time
- Uses DISABLE in a profiler that must count every call
- Claims a fully disabled tool costs exactly zero