Juha-Matti Santala
Community Builder. Dreamer. Adventurer.

Improved print readability with pprint

Batteries included is a blog series about the Python Standard Library. Each day, I share insights, ideas and examples for different parts of the library. Blaugust is an annual blogging festival in August where the goal is to write a blog post every day of the month.

Printing data into the console is one of the first things any developer who starts learning Python does. The classic Hello World application in Python is a single line of print:

print('Hello world!')

It is also a wonderful tool in the toolbox when debugging software.

But printing out nested or complex data structures leads to hard to read and understand (example from pprint documentation):

import json
from urllib.request import urlopen

with urlopen('https://pypi.org/pypi/sampleproject/json') as resp:
  project_info = json.load(resp)['info']
  print(project_info)

prints the project information in a long single line

{'author': '', 'author_email': '"A. Random Developer" <author@example.com>', 'bugtrack_url': None, 'classifiers': ['Development Status :: 3 - Alpha', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Topic :: Software Development :: Build Tools'], 'description': '# A sample Python project\n\n![Python Logo](https://www.python.org/static/community_logos/python-logo.png "Sample inline image")\n\nA sample project that exists as an aid to the [Python Packaging User\nGuide][packaging guide]\'s [Tutorial on Packaging and Distributing\nProjects][distribution tutorial].\n\nThis project does not aim to cover best practices for Python project\ndevelopment as a whole. For example, it does not provide guidance or tool\nrecommendations for version control, documentation, or testing.\n\n[The source for this project is available here][src].\n\nThe metadata for a Python project is defined in the `pyproject.toml` file,\nan example of which is included in this project. You should edit this file\naccordingly to adapt this sample project to your needs.\n\n----\n\nThis is the README file for the project.\n\nThe file should use UTF-8 encoding and can be written using\n[reStructuredText][rst] or [markdown][md use] with the appropriate [key set][md\nuse]. It will be used to generate the project webpage on PyPI and will be\ndisplayed as the project homepage on common code-hosting services, and should be\nwritten for that purpose.\n\nTypical contents for this file would include an overview of the project, basic\nusage examples, etc. Generally, including the project changelog in here is not a\ngood idea, although a simple “What\'s New” section for the most recent version\nmay be appropriate.\n\n[packaging guide]: https://packaging.python.org\n[distribution tutorial]: https://packaging.python.org/tutorials/packaging-projects/\n[src]: https://github.com/pypa/sampleproject\n[rst]: http://docutils.sourceforge.net/rst.html\n[md]: https://tools.ietf.org/html/rfc7764#section-3.5 "CommonMark variant"\n[md use]: https://packaging.python.org/specifications/core-metadata/#description-content-type-optional\n', 'description_content_type': 'text/markdown', 'docs_url': None, 'download_url': '', 'downloads': {'last_day': -1, 'last_month': -1, 'last_week': -1}, 'dynamic': None, 'home_page': '', 'keywords': 'sample,setuptools,development', 'license': 'Copyright (c) 2016 The Python Packaging Authority (PyPA)  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ', 'maintainer': '', 'maintainer_email': '"A. Great Maintainer" <maintainer@example.com>', 'name': 'sampleproject', 'package_url': 'https://pypi.org/project/sampleproject/', 'platform': None, 'project_url': 'https://pypi.org/project/sampleproject/', 'project_urls': {'Bug Reports': 'https://github.com/pypa/sampleproject/issues', 'Funding': 'https://donate.pypi.org', 'Homepage': 'https://github.com/pypa/sampleproject', 'Say Thanks!': 'http://saythanks.io/to/example', 'Source': 'https://github.com/pypa/sampleproject/'}, 'provides_extra': None, 'release_url': 'https://pypi.org/project/sampleproject/3.0.0/', 'requires_dist': ['peppercorn', "check-manifest ; extra == 'dev'", "coverage ; extra == 'test'"], 'requires_python': '>=3.7', 'summary': 'A sample Python project', 'version': '3.0.0', 'yanked': False, 'yanked_reason': None}

If we instead print the project information with pprint.pprint, we get an output that is formatted on multiple lines with indentations:

import pprint

import json
from urllib.request import urlopen

with urlopen('https://pypi.org/pypi/sampleproject/json') as resp:
  project_info = json.load(resp)['info']
  pprint.pprint(project_info)

prints out

{'author': '',
 'author_email': '"A. Random Developer" <author@example.com>',
 'bugtrack_url': None,
 'classifiers': ['Development Status :: 3 - Alpha',
                 'Intended Audience :: Developers',
                 'License :: OSI Approved :: MIT License',
                 'Programming Language :: Python :: 3',
                 'Programming Language :: Python :: 3 :: Only',
                 'Programming Language :: Python :: 3.10',
                 'Programming Language :: Python :: 3.11',
                 'Programming Language :: Python :: 3.7',
                 'Programming Language :: Python :: 3.8',
                 'Programming Language :: Python :: 3.9',
                 'Topic :: Software Development :: Build Tools'],
 'description': '# A sample Python project\n'
# output truncated for brevity

Editor snippet for quick debugging

When debugging with prints, having to import the module, run your prints and then unimport it to avoid leaving unused debug imports in your code can be cumbersome compared to running built-in print statements.

Marijke Luttekes shared a snippet she created for VS Code that I’ve been using with great success ever since. By running the snippet, a code block is inserted at the cursor location with

import pprint  # isort: skip
pp = pprint.PrettyPrinter(indent=2)
print('-' * 60)
pp.pprint()
print('-' * 60)

with the cursor inside the pprint() function call so you can directly type in what you’re looking to print out. The addded ------- lines before and after the print help find the print from the console output.

With the import, initiation and printing all in one tidy block, it becomes easier to add and remove, lowering the friction of use and making it more likely that you use the tool when debugging.

Big thanks to Marijke for this idea! You can find the instructions on how to add it to your VS Code from her blog post. If you’re using a different editor, you’ll have to look into how to build snippets for your editor yourself but learning how to create and use them is a skill worth learning.

Note on pprint.pp

Hugo kindly pointed out in Mastodon that pprint.pp could be a better default to use as it preserves the order of keys in dictionary unlike pprint.pprint which is an alias to pprint.pp with sort_dicts=True.