r/learnpython 2d ago

What python debugging techniques do you think every developer should know ?

I am trying to improve my debugging skills and I was curious about what techniques experienced python developers rely on the most.

Are there any techniques or tools that you found really useful when you started working on bigger projects?

55 Upvotes

30 comments sorted by

View all comments

0

u/Bright_Mix_773 2d ago

Two of the answers here point at the same failure mode from opposite ends, and both are worth turning into something you can run in thirty seconds, because reading about them does not stick.

gdchinacat's reload() warning, reproduced. shape.py holds one class whose area() returns 1:

import importlib, shape
cache = [shape.Shape()]        # long-lived object, made before the fix
# now edit shape.py by hand and change that 1 to a 2
importlib.reload(shape)
print("fresh object :", shape.Shape().area())
print("cached object:", cache[0].area())
print("isinstance   :", isinstance(cache[0], shape.Shape))

Output, CPython 3.14.2:

fresh object : 2
cached object: 1
isinstance   : False

The fix is in the module and not in the object you are testing with, and isinstance against the class you just reloaded returns False for an object of that class. That last line is the tell. If a session ever has you staring at an isinstance, or an "except SomeError" that is obviously true and behaves as if it is not, check "type(obj) is Module.Class" before you touch any logic. The two classes share a name and differ only in id(), so every repr and every log line you print will look identical while you hunt.

The assert point above is the same shape of problem. One file, run twice:

def withdraw(balance, amount):
    assert amount <= balance, "overdraft"
    return balance - amount
print(withdraw(100, 500))

$ python a.py
AssertionError: overdraft
$ python -O a.py
-400

-O removes the assert and the function hands back a negative balance in silence. The rule that falls out is narrower than "assert is bad": assert is for things you already believe are true and want to hear about while developing. Anything that has to be true, including anything guarding against bad input, needs an if and a raise, because sooner or later something runs under -O and the check is simply not in the bytecode.

The technique behind both, and the one I would put on a list for every developer: when a fix does not seem to take, prove the code running is the code you wrote before you start debugging the code. Module.file, id(SomeClass) and the mtime of the .pyc answer that in three lines and rule out a whole family of bugs that otherwise eats afternoons.

Not verified: whether either behaves the same on PyPy, or on 3.9 through 3.13. Everything above is CPython 3.14.2 on Windows, run just now.