Quick Tip on Better Python Debugging

I love printing stuff. I print variables. I print properties of variables. I print empty strings. Sometimes, I print basic ascii art just to pretty up the output. I find it a common occurrence to have more print statements than actual code.

But I recently came across a tool that curbed my habit: IPython, an interactive Python shell. Here’s a TLDR of how it works:

Step 1) Install it

(testing) ktruong:~ ktruong$ pip install IPython

Step 2) Insert ‘from IPython import embed; embed();’ where you’d normally put a ‘print’:

some_buggy_file.py

Step 3) Run the program and debug:

That’s it.

Why use this over normal prints? Because this is a super print. You still only write one line of code to debug but now this allows you to not only print, but also inspect and modify anything and everything in the running context without having start and stop to change what you’re printing.

If that made sense then great. If you’re still interested, let’s walk through a typical example of how you’d use this in your day-to-day:

You’re making an API with Python, Django, and Django Rest Framework because it’s awesome. You’re also writing tests to test your API because that’s also awesome. You’ve written out your views, serializers, and models, and you’ve just finished up your test_views.py:

test_views.py:

…and you run the test:

Well, something went wrong and it seems like my Hand object has a NoneType for a deck instead of a Deck object. Is it models.py?

models.py:

Looks pretty ok to me. Maybe it’s serializers.py:

serializers.py:

It’s possible something went wrong with the process of serializing, but that’s a lot of work to look up all the possible hooks in the docs, so maybe a bigger clue will stick out in views.py:

views.py:

The line in the perform_create method where it tries to retrieve a deck seems to be the right spot to look. I wonder if it hits the try block properly, and if so then does an error get raised and caught in the except block? My normal way of debugging would be to sprinkle prints everywhere:

Result:

So it hit the try block which is great, but it couldn’t find the deck? I could load a bunch more prints to pick apart the response properties, but the code’s already cluttered with prints and I don’t know how many more prints I will have to place to inspect the objects I want so I use IPython:

views.py:

Now I can debug to my heart’s desire:

Oh, so the request data has no deck id. That means the bug is probably in test_views.py and upon looking again I realize that I forgot to save the model instance:

Let’s remove our prints and IPython and see if the test passes now:

(goards) ktruong:converge_api ktruong$ python manage.py test goals.tests.test_views.TestHands.test_post_hand_with_deck
Creating test database for alias ‘default’…
System check identified no issues (0 silenced).
.
 — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — 
Ran 1 test in 0.051s
OK
Destroying test database for alias ‘default’…
(goards) ktruong:converge_api ktruong$

Dot.

Of course if you just paid closer attention than I did you would have caught the bug without any extra work. But hopefully you learned something new and maybe it’ll help you next time you’re about to drop a block of prints. You can use this in any Python program as far as I know, and I’ve used it extensively while developing Django projects. A little note when importing IPython into your file: beware of importing it within loops.

def this_is_a_loop():
…: for i in range(50):
…: from IPython import embed; embed();
…: print(i)

If you run IPython in that loop, you’re going to have to type ‘exit()’ 50 times as IPython will initialize an interactive Python shell 50 times.

Happy debugging!


I’m a developer who documents new tools and concepts I come across and find interesting enough to share. Please click that heart button and/or leave a comment so I know what kind of content you’re looking for.