r/learnpython 17h 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?

Upvotes

25 comments sorted by

u/pachura3 14h ago

Logging > debugging

Also, assert

u/gdchinacat 14h ago

For debugging assert is useful, but I have seen it used improperly in production code. IMO the problem with assert is you can turn it off. If it actually provided safety beyond debugging to identify whether developer assumptions about the state of things is correct, then it shouldn't be something that can be turned off in the most critical environment. A disabled assert that is disabled allows code that is not designed or tested to execute in that state to proceed. The application has undefined results at that point, and is likely to result in far removed issues that are much more difficult to troubleshoot than if the assert had failed and clearly said what the issue is.

That said, I do use asserts, but only for validating *my* understanding of the code while debugging. They are (usually, rules are meant to be broken) removed along with prints before commit, and almost certainly before review. If there is value in them persisting a validation exception is much better because the code that ensures correct execution can't be disabled. The primary exception to this is when I have to debug an issue in production (because that's the only place it exists) *AND* I know production doesn't run with assertions disabled. In this case it is a clear indicator to future me and others there is a lack of confidence in that code or the callers of that code.

u/RevRagnarok 8h ago

logging to a file.

u/nivaOne 13h ago

The Print command is a good friend of mine.

u/Langdon_St_Ives 13h ago

Ok here's a technique every developer should know: ditch those random print's and instead start logging in a structured way. Why? Because instead of removing all those debugging print statements again, you just change the log level back to info and they're gone. You want them back in three months? Change log level to debug, done.

u/this_knee 11h ago

Totally agree. Logging fam.

u/burlyginger 8h ago

Fat better to use breakpoint() and have a repr at that point in your code.

u/DanceHackRock 10h ago

The poor man's debugger is never wrong.

u/Gnaxe 16h ago

Breakpoint and print, obviously, but also

  • importlib.reload()
  • pdb.pm()
  • code.interact()
  • doctest
  • unittest.mock
  • inspect

Shorten your feedback loops. Make the state transparent. Write tests. Minimize the state you have to deal with in the first place (FP). Use queues instead of locks. Use plain data instead of bespoke classes, and at least implement __repr__() if you must. 

u/gdchinacat 14h ago

importlib.reload() has wasted more debugging time for me than it has saved. If what you are debugging has any persistent state (ie a cache or a long lived data model) you can have old versions of objects in it that "come back to life" and can mislead you into thinking you didn't fix an issue when you actually did. It took a couple cases of wasting a few hours each time for me to swear it off for good. It is not worth risking that frustating waste of time for the few seconds it saves. If you have a huge app that takes a while to load it can save time, but I will argue you shouldn't be testing fixes to issues against your app, but rather through a unit test that reproduces the issue that you can run on its own. Recommending people learning python to use it is a bad idea IMO because they don't yet understand all the edge cases they need to know to use it effectively.

u/this_knee 16h ago

I don’t like the breakpoint and print strategy. I always end up either forgetting about the print statements left behind or then have to search through files and classes for print statements that were just for debugging. This strategy slows me down and incentivizes me to not do it.

However , what I do prefer is placing debug level log statements. Ie a properly built logging system that allows me to simply turn off all my print statements with one quick configuration change. This is something g I like and incentivizes me to do it often because it’s a print statement I know I can come back to if future trouble happens. Do be careful…make sure those log statements only build their soon to be printed message if and only if their log level is enabled. Ie should be a no op when logging is sent to silent mode.

Thanks for coming to my Ted Talk.

u/Russjass 12h ago

I am not a pro developer, but have an app with a detailed logging system. When I built the logging system I thought "yay, no more chasing print statements after debugging".

After fixing four bugs, i used print statements for the fifth becuase the log filled up with spam from the lig statements of the first bugs

Perhaps I dont have my logging configured right

u/this_knee 11h ago

Key to a good logging is the info that labels the log message. Eg , all my log statements come with: log level ; date; time; name of code file log message comes from; name of function the log message comes from; line number ; and then the log message from that particular line of code.

This lets me easily cut through past log messages and focus on just the ones I need in a given scenario.

u/gdchinacat 14h ago

Use the REPL to see how things work and verify they work as you expec. Sometimes code that looks reasonable doesn't work quite like it needs to, so verify code does what you think it does can help find subtle bugs and avoid chasing the wrong issue.

u/AFL_gains 9h ago

Import pdb; pdb.set_trace()

u/qwerteccia 9h ago

Use your IDE debugger and put breakpoints to see variable's values anywhere you need.

u/Lionh34rt 16h ago

Print

u/StrayFeral 11h ago

logging. pytest. print()

u/simonsanchezart 9h ago

print(*collection, sep='\n') to print collections

u/0xGollumDev 1h ago

Stuff not already in the thread:

  • git bisect for "it worked last week." Give it a known-good commit and a known-bad one, it binary-searches history and lands you on the exact commit that broke it. Turns a vague regression into a small diff.
  • Post-mortem debugging: python -m pdb -c continue script.py, or in the REPL right after an exception, import pdb; pdb.pm(). Drops you at the frame where it blew up with all locals intact — no editing code to add breakpoints.
  • Shrink before you debug. Copy the failing path into a fresh file and delete everything not needed to still reproduce it. Most of the time the bug is obvious at ~15 lines and you never open a debugger.
  • Hangs / "stuck forever": run with python -X faulthandler and send SIGABRT, or call faulthandler.dump_traceback_later(10) to auto-dump a traceback if the process is still alive in 10s. Shows the exact line it's spinning on.
  • python -W error turns that warning you've been ignoring into an exception with a full traceback.

u/Bright_Mix_773 10h 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.