Programming with AI
ENRU
4.5

Debugging with print

when it is enough and how to do it properly

What it is

Dropping in a print and looking at what is in a variable is the simplest way to debug. It is conventionally treated as unserious, and for most tasks it is faster than a debugger and requires no setup at all.

This article is about printing in a way that actually helps, and about when you do need a debugger after all.

What it looks like in code

Bad

print(data)
print(x)
print("here")

You run it, get three cryptic lines in a row and can't tell which came from where.

Good

print(f"[parse] items={len(items)} first={items[0] if items else None!r}")
print(f"[to_numbers] temp={it['temp']!r} type={type(it['temp']).__name__}")

Three habits that make printing useful:

A label. [parse] tells you at once where the line came from. With a dozen prints it saves all of the time.

!r instead of plain output. repr shows the quotes and the invisible characters. The difference is decisive:

value = " 42 "
print(f"value={value}")      # value= 42
print(f"value={value!r}")    # value=' 42 '

In the second case you can see immediately that the string has spaces in it — and those spaces are exactly why int() was failing.

The type, not only the value. '4' and 4 look almost the same and behave differently. type(x).__name__ settles the question.

Printing a structure

For dictionaries and lists this is more convenient:

import json
print(json.dumps(data, ensure_ascii=False, indent=2)[:500])

ensure_ascii=False keeps non-English text readable instead of showing escape codes. The [:500] slice keeps the terminal from flooding when there is a lot of data.

When you need a debugger

Printing is enough while you know what to print. A debugger is for when you don't:

PrintDebugger
you know which variables to look atyou don't know what is going on at all
one or two passesyou need to go step by step
the code runs quicklyevery run is slow
everything is in your own codethe error is somewhere in a library

The cheapest debugger in Python is one line in the right place:

breakpoint()

The program stops and you land in an interactive shell where any variable can be inspected. c continues, n is the next line, q quits.

What to tell the AI

Prompt
I don't understand why this ends up as an empty list.
Where should I put two prints to make that visible?
Don't change the code, just name the places and what to print.
Prompt
Add debug prints to all three functions: a label, the key
values through !r, and their types. I'll remove them later.

And the mandatory final step:

Prompt
Remove all the debug prints we added.
Don't touch the log.* logging.

What to look for

Prints left in the code. The classic: debugged it, forgot to remove them, it shipped to the server and now clutters the output. Before committing, search the diff for print(.

Print instead of logging in long-lived code. In a bot or on a server print disappears along with the terminal. There you need logging (article 4.2).

Printing in the wrong place. If the output doesn't change for clearly different inputs, the print is probably in the wrong branch — or execution never reaches it.

Secrets in a print. print(config) together with the token, and now it is in the terminal history, or in a screenshot sent to a chat.

Cheat sheet

  • A label at the start of the line: [parse] ....

  • Always !r — it shows spaces, quotes and None.

  • Print the type, not only the value.

  • Dictionaries — via json.dumps(..., ensure_ascii=False) and a slice.

  • Don't know what to look at — breakpoint().

  • Before committing, check the diff for leftover print(.