skip to content

In pdb, how do you print a variable whose name collides with a command such as n or c?

level: middleimportance: nice to knowfreq 20%

answer

  1. The prompt is a command loop first
  2. Single-letter names shadow debugger commands
  3. Two characters make the intent explicit
  4. One prefix forces a Python statement
  5. Pretty-printing helps with nested structures

basics

~20 s

The pdb prompt matches its own commands first, so typing n runs next. Use p n to print the value, pp n to pretty-print it, or an exclamation-mark prefix to run the line as Python.

solid answer

~50 s

The `(Pdb)` prompt is a command interpreter, not a Python REPL: it matches the first word against its command table before falling back to evaluating the line. So at a frame where a local is called `n`, `c`, `l`, `s`, `q`, `r`, `a`, `b`, `w`, `u` or `d`, typing the bare name runs the command instead. Three escapes exist. `p n` prints `repr(n)` evaluated in the current frame. `pp n` does the same through `pprint`, which wraps and indents nested structures — much more readable for a dict of lists. A leading `!` forces the rest of the line to be executed as a Python statement, which is also how you assign to such a name: `!n = 3`. Related, `display expr` re-prints an expression each time it changes as you step, and `interact` opens a full Python REPL in the paused frame.

code

python · 5 lines
python
from pprint import pprint

route = {"legs": [{"id": i, "stops": [i, i + 1], "fee": 12.5} for i in range(4)]}
print(route)
pprint(route, width=40)

go deeper

for a junior

Get into the habit of typing p before a name at the debugger prompt. It always means print, it never accidentally resumes the program, and it works for whole expressions rather than just variables.

for a middle

Explain why the collision happens: the prompt matches commands before evaluating Python. Know the three escapes, p, pp and the leading exclamation mark, and that assignment specifically needs the prefix form.

for a senior

Show the operational reflex. Accidentally typing a bare c resumes a process you paused on purpose and destroys the state you were inspecting, so build habits, use display for values you watch repeatedly, and reach for interact when exploration needs real multi-line code.

for a principal

The wider point is that an interactive prompt is a poor system of record. Encourage teams to turn repeated debugger inspections into assertions, structured logs or tests, so that what one engineer learned at a prompt is captured somewhere the next incident can read it.

## Why the collision exists `pdb` is built on Python's standard command-loop machinery. Every line you type at the `(Pdb)` prompt is first split, and its first word is looked up in the debugger's command table; only if there is no match does the debugger fall back to executing the line as Python in the paused frame. That fallback is what makes the prompt feel like a REPL, and the lookup that comes first is what makes it not one. Python's habit of naming loop counters `n`, `c` or `i` collides with that table constantly. `n` is `next`, `c` is `continue`, `s` is `step`, `r` is `return`, `q` is `quit`, `l` is `list`, `u` is `up`, `d` is `down`, `w` is `where`, `a` is `args`, `b` is `break`, `j` is `jump`, `h` is `help`. Typing a bare `c` when you meant "show me `c`" does not show you anything — it resumes the program, and the state you wanted to inspect is gone. This is one of the few debugger gotchas that actively destroys evidence, which is why it is worth knowing before you need it. ## The three escapes ### `p expr` `p` evaluates the expression in the current frame and prints its `repr`. `p n` is unambiguous no matter what `n` is bound to, and `p` works for arbitrary expressions, not just names: `p len(rows)`, `p row["id"] == 6789`, `p type(x).__mro__`. Because it is `repr` and not `str`, `p` on a string shows the quotes and the escapes — which is exactly what you want at a debugger prompt, since `'42'` and `42` print identically under `str`. ### `pp expr` `pp` prints the same value through pretty-printing: nested dicts and lists are wrapped and indented instead of running off the right edge of the terminal. For anything with structure — a parsed record, a nested config, a list of dicts — `pp` is the one you want. For a scalar the two are identical. ### `!statement` A leading exclamation mark tells the debugger "do not look this up as a command; run it as a Python statement in this frame". `!n` prints nothing on its own (an expression statement discards its value), but `!n = 3` rebinds the local, and `!import json` or `!rows.clear()` do what they say. The general rule: `p` for reading, `!` for anything that is a statement rather than an expression, and `!` whenever the first token is ambiguous. Assignment is the case that really needs `!`. Reading is well served by `p`; writing has no `p`-shaped equivalent. ## Two neighbours worth knowing ### `display expr` `display` registers an expression against the current frame. Every time the debugger stops there again — after each `n` or `s` — it re-evaluates it and prints it *if the value changed*. Watching an accumulator or a loop index without retyping `p` after every step is exactly its purpose, and `undisplay` removes it. Displays are per-frame, so one registered in a callee does not follow you to the caller. ### `interact` `interact` starts a full interactive Python interpreter using the paused frame's globals and a copy of its locals. Inside it there are no command collisions at all, because it is a real REPL — the price is that assignments there do not propagate back to the frame's actual locals when you leave. Use it when you want to explore with multi-line code; use `p`/`!` when you want to touch the real frame. ## The habit to build Experienced users type `p` almost reflexively rather than deciding case by case whether the name they want happens to collide. It costs two characters, it never surprises you, and it removes an entire class of "I meant to look, and instead I ran the program to completion" mistakes. The same instinct explains why pressing Enter at the prompt repeats the last command — cheap for `n`, dangerous if the last command was `c`.

  • Why is pressing Enter at the pdb prompt risky?
    An empty line repeats the previous command. That is convenient when you are stepping with n, but if the last command was c the program resumes, and if it was q the session ends. The habit that saves you is the same one that avoids name collisions: type the command you mean, especially after anything that resumes execution.
  • How does display differ from just typing p after every step?
    display registers the expression once against the current frame, and pdb re-evaluates and prints it automatically whenever it stops there again — but only when the value has changed, so an unchanging value stays quiet. It is a watch list rather than a manual read, and undisplay removes an entry. Displays belong to the frame, so they do not follow you into a callee.

saying these in an interview costs you the question

  • Assumes the pdb prompt is a plain Python REPL
  • Thinks p and pp differ in what they evaluate, not how they format
  • Believes the exclamation prefix runs a shell command
  • Says p prints str rather than repr
  • Cannot assign to a local at the prompt at all
  • Thinks display prints on every step regardless of change

context