Readably Display Arbitrary Python Data
For a CLI program, or any program with text output, many programmers have been challenged with displaying data in the terminal in a readable format.
Either you are making a script to display data from a database via the python module sqlalchemy
, or you are displaying the results of a RESTful API call via the python module requests
, you likely have a handful of arbitrary complex python data structures to display. These might be a set of lists, dicts, ints, datetimes, and other objects.
Follow this guide and lets code together a way to display this data in a way you can easily read from the terminal.
In order to have data to display, I will use a complex data structure in the variable fake_data
.
1 | complex_data=(['hklmvKwrCboZpiwfUhoP', 'bass/explore/search/terms.php', 1678], {'party': 'utyUSpwEkRWicOcjDIeV', 'responsibility': 'williams/list/search.php', 'conference': 'TSrylvPlLNZifFJiqoFS'}, {'culture': {'apples': 'lcZORekRWSmDqfrQRPjc', 'oranges': ['hrobinsonhotmailcom', 6203562684090.42, 'tDNiboBzQtgWdFApUdYC'], 'pairs': {1: '2011-01-12 15:14:13', 2: 'DQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSmJqRG', 3: ['2000-1-10 09:05:01', 'parker-poole/blog/posts/category/login.php']}}}) |
One way of displaying text is using the popular library pprint
which outputs in a format like this:
1 | import pprint |
(['hklmvKwrCboZpiwfUhoP', 'bass/explore/search/terms.php', 1678],
{'conference': 'TSrylvPlLNZifFJiqoFS',
'party': 'utyUSpwEkRWicOcjDIeV',
'responsibility': 'williams/list/search.php'},
{'culture': {'apples': 'lcZORekRWSmDqfrQRPjc',
'oranges': ['hrobinsonhotmailcom',
6203562684090.42,
'tDNiboBzQtgWdFApUdYC'],
'pairs': {1: '2011-01-12 15:14:13',
2: 'DQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSmJqRG',
3: ['2000-1-10 09:05:01',
'parker-poole/blog/posts/category/login.php']}}})
This is already a mostly readable way of displaying data, however this doesn’t work for user-facing output. All the python-specific syntax is left in the output, which end-users don’t care about. Also, the data isn’t all lined up which makes it slightly more difficult to read.
pprint
just isn’t pretty enough. What we need is a way to display complex data in a neat and human-readable fashion.
Follow along as we code a function to display arbitrary data.
Defining the problem
In computer science, there is a concept called Dynamic Programming, where you solve a larger problem by solving the sub-problems first. In this case, the larger problem is displaying any arbitrary data, however the sub-problems are displaying units of data which python knows how to display through the built-in str
conversion.
All python objects have a string representation through the __str__()
function, so the smallest sub-problem to solve is to display any arbitrary string. After solving that problem, we can think about displaying lists of strings and dicts of strings. Even lists of dicts of strings.
Displaying a string
The first step to displaying any arbitrary data is to display a string. You may think it’s as easy as print(my_string)
, however that isn’t accounting whether the string is being displayed somewhere in the middle of the terminal window.
1 | We don't want the text to wra |
So how do we fix this?
Write a string-formatter function that knows where to wrap and how much to indent the next line.
1 | def format_string(string, term_width=80, indent=0): |
This function displays a string without any weird line-wrapping issues. Also, when displaying in the center of the terminal window, the next lines in the string are indented via the indent
function argument.
The term_width
argument is set to 80 to simulate a 80-character width terminal, however in a real terminal this can be obtained by reading the value of shutil.get_terminal_size()[0]
.
Now onto the fun stuff. With the function we made above, extra-long strings of text are formatted very nicely, like so:
1 | short_string = 'a short string' |
a short string
1 | very_long_string = 'a ' + 'very '*20 + 'long string for displaying the format_string\'s text-wrapping feature. As you can see the text does not wrap in the middle of a word.' |
a very very very very very very very very very very very very very very very
very very very very very long string for displaying the format_string's
text-wrapping feature. As you can see the text does not wrap in the middle of a
word.
Also, lets say we want to print a paragraph of text with a label on front. This is possible through format_string
‘s indenting of every line except the first line.
An example of this is below.
1 | label = 'the label for my text:' |
the label for my text: a very very very very very very very very very very very
very very very very very very very very very long string
for displaying the format_string's text-wrapping
feature. As you can see the text does not wrap in the
middle of a word.
Displaying a list
Now that we can display arbitrary strings, now is time for displaying lists of strings that call the format_string
function with the correct indent data.
We will work up to displaying a list of any arbitrary data, however for now lets simply display a list of strings.
1 | def format_list(lst, term_width=80, indent=0): |
This function returns the format_string
representation of each item in the list, using ' - '
as the label for each item. For handling nested lists, format_list
is called recursively when one of its elements is also a list.
Here are a couple examples of formatted lists:
1 | print(format_list([1, 2, 3])) |
- 1
- 2
- 3
1 | print(format_list([short_string, very_long_string])) |
- a short string
- a very very very very very very very very very very very very very very very
very very very very very long string for displaying the format_string's
text-wrapping feature. As you can see the text does not wrap in the middle
of a word.
You can see that the text wrapping indents nicely for elements of the list. Lets see how the function does with nested lists.
1 | list_of_lists = [['apples','oranges','pears'],'peaches',[['tangerines','bananas','avacados'],['carrots','oranges','beans']]] |
- - apples
- oranges
- pears
- peaches
- - - tangerines
- bananas
- avacados
- - carrots
- oranges
- beans
Now we’re talking. This nested list of lists of lists printed in a nice readable format to the screen.
1 | print(format_list(complex_data)) |
- - hklmvKwrCboZpiwfUhoP
- bass/explore/search/terms.php
- 1678
- {'party': 'utyUSpwEkRWicOcjDIeV', 'responsibility':
'williams/list/search.php', 'conference': 'TSrylvPlLNZifFJiqoFS'}
- {'culture': {'apples': 'lcZORekRWSmDqfrQRPjc', 'oranges':
['hrobinsonhotmailcom', 6203562684090.42, 'tDNiboBzQtgWdFApUdYC'], 'pairs':
{1: '2011-01-12 15:14:13', 2:
'DQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSm
JqRGDQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSmJqRG', 3: ['2000-1-10 09:05:01',
'parker-poole/blog/posts/category/login.php']}}}
This function’s attempt at printing dicts in a list however epicly fails. To fix this problem, we need a dict-formatter function that is called when formatting an element of a list containnig a dictionary datatype.
Displaying a dict
Now that we have a list-formatting function, we need to format dict-datatypes as well.
Formatting the dict is a bit different than formatting a list. The main difference is that every item in the dict has a key as a label on front of each value. This means that we can’t shortcut the problem with something like format_list(dic.items())
.
Lets use the same strategy of populating a list of lines to return, and then combining the lines into one string.
1 | def format_dict(dic, term_width=80, indent=0): |
This simple function for displaying dicts sets up a list of lines to display by calling corresponding format
functions recursively for each element in the dict.
1 | nested_dict = {1: 'apples', 123: {'oranges': 20, 'tangerines': 30, 'carrots': 0}, 'something': list(range(3))} |
1: apples
123: oranges: 20
tangerines: 30
carrots: 0
something: - 0
- 1
- 2
There is still one problem. The format_list
function doesn’t call format_dict
when it finds a dict-type object. To fix this issue lets move on to the next step, which is combining all 3 functions.
Combining all 3 string-formatting functions to display any arbitrary data in one function
1 | def format_obj(obj, term_width=80, indent=0): |
1 | print(format_obj(complex_data)) |
- - hklmvKwrCboZpiwfUhoP
- bass/explore/search/terms.php
- 1678
- party: utyUSpwEkRWicOcjDIeV
responsibility: williams/list/search.php
conference: TSrylvPlLNZifFJiqoFS
- culture: apples: lcZORekRWSmDqfrQRPjc
oranges: - hrobinsonhotmailcom
- 6203562684090.42
- tDNiboBzQtgWdFApUdYC
pairs: 1: 2011-01-12 15:14:13
2: DQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSm
JqRGDQsaoDQPYHolbVSmJqRGDQsaoDQPYHolbVSmJqRGDQsaoDQPYHol
bVSmJqRG
3: - 2000-1-10 09:05:01
- parker-poole/blog/posts/category/login.php
Now to display a random json file from the internet
1 | import requests |
1 | print(dict_from_json) |
{'posts': [{'id': 1, 'title': 'Post 1'}, {'id': 2, 'title': 'Post 2'}, {'id': 3, 'title': 'Post 3'}], 'comments': [{'id': 1, 'body': 'some comment', 'postId': 1}, {'id': 2, 'body': 'some comment', 'postId': 1}], 'profile': {'name': 'typicode'}}
1 | print(format_obj(dict_from_json)) |
posts: - id: 1
title: Post 1
- id: 2
title: Post 2
- id: 3
title: Post 3
comments: - id: 1
body: some comment
postId: 1
- id: 2
body: some comment
postId: 1
profile: name: typicode
This neat representation of nested python lists and dicts is easy to read and flexible for displaying all types of data.
Feel free to use my code and apply it to your own projects.
Happy coding!
Comments
<code><pre>insert.code.here()<pre/><code/>