4

Tips for debugging with print()

 2 years ago
source link: https://adamj.eu/tech/2021/10/08/tips-for-debugging-with-print/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Tips for debugging with print()

Adam Johnson

Home | Blog | Projects | Colophon | Contact

Tips for debugging with print()

2021-10-08

A machine of much printing.

If you’re embarrassed at debugging with print(), please don’t be - it’s perfectly fine! Many bugs are easily tackled with just a few checks in the right places. As much as I love using a debugger, I often reach for a print() statement first.

Here are five tips to get the most out of debugging with print().

1. Debug variables with f-strings and =

Often we use a print() to debug the value of a variable, like:

>>> print("widget_count =", widget_count)
widget_count = 9001

On Python 3.8+ we can use an f-string with the = specifier to achieve the same with less typing. This specifier prints the variable’s name, “=”, and the repr() of its value:

>>> print(f"{widget_count=}")
widget_count=9001

Less typing for the win!

We aren’t limited to variable names with =. We can use any expression:

>>> print(f"{(widget_count / factories)=}")
(widget_count / factories)=750.0833333333334

If you prefer spaces around your =, you can add them in the f-string and they will appear in the output:

>>> print(f"{(widget_count / factories) = }")
(widget_count / factories) = 750.0833333333334

Neat!

2. Make output “pop” with emoji

Make your debug statements stand out among other output with emoji:

print("👉 spam()")

You can also then jump to your debug output with your terminal’s “find” function.

Here are some good emojis for debugging, which may also help express associated emotions:

  • 👉 “Reached this point”
  • ❌ “Failed as expected”
  • ✅ “Worked randomly”
  • 🥲 “Trying hard”
  • 🤡 “I feel like a software clown
  • 🤯 “WTF”

To type emoji faster, use the keyboard shortcut for your OS:

  • Windows: Windows Key + .
  • macOS: Control + Command + Space
  • Ubuntu: Control + .
  • Other Linuxes: 🤷‍♂️ consult documentation

3. Use rich or pprint for pretty printing

Rich is a terminal formatting library. It bundles many tools for prettifying terminal output and can be installed with pip install rich.

Rich’s print() function is useful for debugging objects. It neatly indents large, nested data structures, and adds syntax highlighting:

Screenshot using rich.print()

Cool beans.

Using from rich import print replaces the builtin print() function. This is normally safe since the Rich version is designed as a drop-in replacement, but it does mean everything passed to print() is formatted. To preserve our application’s non-debug output exactly, we can use an import alias like from rich import print as rprint, keeping rprint() for debugging.

For more info see the Rich quick start guide.

If you’re not at liberty to install Rich, you can use Python’s pprint() function instead. The extra “p” stands for “pretty”. pprint() also indents data structures, albeit without colour or style:

>>> from pprint import pprint
>>> pprint(luke)
{'birth_year': '19BBY',
 'films': [3, 4, 5, 6, 7, 8],
 'id': 1,
 'name': 'Luke Skywalker'}

Handy.

4. Use locals() to debug all local variables

Local variables are all the variables defined within the current function. We can grab all the local variables with the locals() builtin, which returns them in a dictionary. This is convenient for debugging several variables at once, even more so when combined with Rich or pprint.

We can use locals() like so:

from rich import print as rprint


def broken():
    numerator = 1
    denominator = 0
    rprint("👉", locals())
    return numerator / denominator

When we run this code, we see the dictionary of values before the exception:

>>> broken()
👉 {'numerator': 1, 'denominator': 0}
Traceback (most recent call last):
  ...
ZeroDivisionError: division by zero

There’s also the globals() builtin which returns all the global variables, that is, those defined at the module scope, such as imported variables and classes. globals() is less useful for debugging since global variables don’t usually change, but it is good to know about.

5. Use vars() to debug all of an object’s attributes

The vars() builtin returns a dictionary of an object's attributes. This is useful when we want to debug many attributes at once:

>>> rprint(vars(widget))
{'id': 1, 'name': 'Batara-Widget'}

Brillo.

(vars() without an argument is also equivalent to locals(), in about half the typing.)

vars() works by accessing the __dict__ attribute of the object. Most Python objects have this as the dictionary of their (writeable) attributes. We can also use __dict__ directly, although it’s a little more typing:

>>> rprint(widget.__dict__)
{'id': 1, 'name': 'Batara-Widget'}

vars() and __dict__ don’t work for every object. If an object’s class uses __slots__, or it’s built in C, then it won’t have a __dict__ attribute.

✨Bonus✨ 6. Try icecream

Update (2021-10-09): Thanks to Malcolme Greene for reminding me of this package.

The icecream package provides a handy debugging shortcut function, ic(). This function combines some of the tools we’ve been looking at. Called without arguments, ic() debugs details about where and when it was called:

from icecream import ic


def main():
    print("Starting main")
    ic()
    print("Finishing")


if __name__ == "__main__":
    main()
$ python example.py
Starting main
ic| example.py:6 in main() at 11:28:27.609
Finishing

Called with arguments, ic() inspects and prints each expression (through source inspection) and its result:

from icecream import ic


def main():
    a = 1
    b = 2
    ic(a, b, a / b)


if __name__ == "__main__":
    main()
$ python example.py
ic| a: 1, b: 2, a / b: 0.5

This includes some syntax highlighting like Rich.

icecream also has some other handy abilities like installing as a builtin so you don’t need to import it. Check out its documentation for more info.

May you ever print() your bugs away,

—Adam


🎉 My book Speed Up Your Django Tests is now up to date for Django 3.2. 🎉
Buy now on Gumroad


Subscribe via RSS, Twitter, or email:

Your email address:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: python

© 2021 All rights reserved.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK