Pretty-print dataclasses prettier with line breaks and indentation

pretty-print, python, python-dataclasses

Solution

The `pprint` package supports pretty printing only since version 3.10 (NB: Python 3.10 was released in 2021).

Example:

[ins] In [1]: from dataclasses import dataclass
         ...:
         ...: @dataclass
         ...: class Point:
         ...:     x: int
         ...:     y: int
         ...:
         ...: @dataclass
         ...: class Coords:
         ...:     my_points: list
         ...:     my_dict: dict
         ...:
         ...: coords = Coords([Point(1, 2), Point(3, 4)], {'a': (1, 2), (1, 2): 'a'})

[ins] In [15]: pprint.pprint(coords, width=20)                                  
Coords(my_points=[Point(x=1,
                        y=2),
                  Point(x=3,
                        y=4)],
       my_dict={'a': (1,
                      2),
                (1, 2): 'a'})

When using Python 3.9 or older, there is the prettyprinter package that supports dataclasses and provides some nice pretty-printing features.

Example:

[ins] In [1]: from dataclasses import dataclass
         ...:
         ...: @dataclass
         ...: class Point:
         ...:     x: int
         ...:     y: int
         ...:
         ...: @dataclass
         ...: class Coords:
         ...:     my_points: list
         ...:     my_dict: dict
         ...:
         ...: coords = Coords([Point(1, 2), Point(3, 4)], {'a': (1, 2), (1, 2): 'a'})

[nav] In [2]: import prettyprinter as pp

[ins] In [3]: pp.pprint(coords)
Coords(my_points=[Point(x=1, y=2), Point(x=3, y=4)], my_dict={'a': (1, 2), (1, 2): 'a'})

The dataclasses support isn't enabled, by default, thus:

[nav] In [4]: pp.install_extras()
[ins] In [5]: pp.pprint(coords)
Coords(
    my_points=[Point(x=1, y=2), Point(x=3, y=4)],
    my_dict={'a': (1, 2), (1, 2): 'a'}
)

Or to force indenting of all fields:

[ins] In [6]: pp.pprint(coords, width=1)
Coords(
    my_points=[
        Point(
            x=1,
            y=2
        ),
        Point(
            x=3,
            y=4
        )
    ],
    my_dict={
        'a': (
            1,
            2
        ),
        (
            1,
            2
        ): 'a'
    }
)

Prettyprinter can even syntax-highlight! (cf. `cpprint()`)

Considerations:

- `prettyprinter` isn't part of the python standard library

- default values aren't printed, at all and as of 2021 there is no way around this

- `prettyprinter` is pretty-printing very slowly, i.e. much slower than the standard `pprint`, e.g. for checking if a value is a default value, it's compared against a default-constructed value

Problem

Python Data Classes instances also include a string representation method, but its result isn't really sufficient for pretty printing purposes when classes have more than a few fields and/or longer field values. Basically I'm looking for a way to customize the default dataclasses string representation routine or for a pretty-printer that understands data classes and prints them prettier. So, it's just a small customization I have in mind: adding a line break after each field while indenting lines after the first one. For example, instead of ``` x = InventoryItem('foo', 23) print(x) # => InventoryItem(name='foo', unit_price=23, quantity_on_hand=0) ``` I want to get a string representation like this: ``` x = InventoryItem('foo', 23) print(x) # => InventoryItem( name='foo', unit_price=23, quantity_on_hand=0 ) ``` Or similar. Perhaps a pretty-printer could get even fancier, such as aligning the `=` assignment characters or something like that. Of course, it should also work in a recursive fashion, e.g. fields that are also dataclasses should be indented more.

Original source