20

Python has a pretty printer (pprint(...)). I would like to make my classes pretty printable. Will pretty print print my instances in a better way, if I provide a certain interface?

The Python documentation in section 8.11 shows different examples, but no example how to make a user defined class pretty printable.

So what interface need my classes to provide?
Is there any other (maybe better) formatter?


Use Case:

I want to pretty print the content of ConfigParser, for which I have create an extended version called ExtendenConfigParser. So I have the possibility to add more functionality or add a matching pretty print interface.

Paebbels
  • 14,791
  • 12
  • 61
  • 127
  • What do you mean by 'pretty printed' class? Like dict? – exprosic Nov 27 '16 at 11:06
  • `pprint(config)` gives only ``. The internal data structures are two nested ordered dictionaries. I would like to print them as 2 nested dicts. I could write a function for this job, but I would like to have a method and/or pprint compatible class. – Paebbels Nov 27 '16 at 11:07
  • I don't think `pprint` offers that functionality. However, you _could_ give your class a [`__format__`](https://docs.python.org/3/reference/datamodel.html#object.__format__) method (in addition to `__repr__` and `__str__` methods) to make it print prettily when it's passed to the `format` built-in function or the `str.format` method. – PM 2Ring Nov 27 '16 at 11:10
  • Related: https://stackoverflow.com/questions/3258072/best-way-to-implement-custom-pretty-printers – Ciro Santilli Путлер Капут 六四事 Sep 03 '20 at 14:11

4 Answers4

13

pprint does not look for any hooks. The pprint.PrettyPrinter uses a dispatch pattern instead; a series of methods on the class that are keyed on class.__repr__ references.

You can subclass pprint.PrettyPrinter to teach it about your class:

class YourPrettyPrinter(pprint.PrettyPrinter):
    _dispatch = pprint.PrettyPrinter._dispatch.copy()

    def _pprint_yourtype(self, object, stream, indent, allowance, context, level):
        stream.write('YourType(')
        self._format(object.foo, stream, indent, allowance + 1,
                     context, level)
        self._format(object.bar, stream, indent, allowance + 1,
                     context, level)
        stream.write(')')

    _dispatch[YourType.__repr__] = _pprint_yourtype

then use the class directly to pretty print data containing YourType instances. Note that this is contingent on the type having their own custom __repr__ method!

You can also plug functions directly into the PrettyPrinter._dispatch dictionary; self is passed in explicitly. This is probably the better option for a 3rd-party library:

from pprint import PrettyPrinter

if isinstance(getattr(PrettyPrinter, '_dispatch'), dict):
     # assume the dispatch table method still works
     def pprint_ExtendedConfigParser(printer, object, stream, indent, allowance, context, level):
         # pretty print it!
     PrettyPrinter._dispactch[ExtendedConfigParser.__repr__] = pprint_ExtendedConfigParser

See the pprint module source code for how the other dispatch methods are written.

As always, single-underscore names like _dispatch are internal implementation details that can be altered in a future version. However, it is the best option you have here. The dispatch table was added in Python 3.5 and is present in at least Python 3.5 - 3.9 alpha.

Martijn Pieters
  • 963,270
  • 265
  • 3,804
  • 3,187
  • Your proposed solution seems to have no harm if my classes already use (multi-)inheritance, right? – Paebbels Nov 27 '16 at 11:16
  • 1
    @Paebbels: do make sure there is a unique `__repr__` method to key to, otherwise I see no potential for harm. – Martijn Pieters Nov 27 '16 at 11:18
  • The ConfigParser class abstracts INI configuration files. If I would like to also print out the INI representation format, then I should use `__format__`, right? Because `pprint(...)` targets Python readable representations. – Paebbels Nov 27 '16 at 11:21
  • 1
    @Paebbels you'll have to experiment with how you'd integrate that with the pprint output (given the indentation and allowance values, for example). – Martijn Pieters Nov 27 '16 at 11:25
  • @RickGraves: it’s still there, even in trunk: https://github.com/python/cpython/blob/a796d8ef9dd1af65f7e4d7a857b56f35b7cb6e78/Lib/pprint.py#L187, but I had a small error in the code, now corrected. Python 2 never had it. – Martijn Pieters Jan 09 '20 at 23:10
  • Sorry, my first comment (deleted) was incorrect. Python 3.6, 3.7 & 3.8 have _dispatch, 2.7 does not. – Rick Graves Jan 09 '20 at 23:34
  • @RickGraves: indeed, as I said, Python 2 never supported this. – Martijn Pieters Jan 09 '20 at 23:46
  • @RickGraves the table was [introduced in 3.5](https://github.com/python/cpython/commit/8e2aa88a40c0c7611a04696d1789da159e40d7f7). – Martijn Pieters Jan 09 '20 at 23:48
2

It is not really a solution, but I usually just make objects serializable and pretty print them like this:

pprint(obj.dict())
aiven
  • 3,262
  • 2
  • 19
  • 42
0

The subclass solution by Martijn Pieters is working for me, and I made it more general by not hard coding foo and bar.

Take out:

    self._format(object.foo, stream, indent, allowance + 1,
                 context, level)
    self._format(object.bar, stream, indent, allowance + 1,
                 context, level)

Substitute (put in):

    for s in vars(object):
        stream.write( '\n%s: ' % s )
        self._format( object.__dict__[s],
                      stream, indent, allowance + 1, context, level )
Rick Graves
  • 357
  • 3
  • 10
0

If you're going to go to all that effort, you might as well superclass pprint to accept a hook, then you'll only have to write all that code once.

It will also work better in cases where your class is defined after you've instantiated a pprint helper with pp = pprint.PrettyPrinter(indent=4).pprint (a bad habit of mine).

Then you can opt-in with any class by using one of these methods [pun not intended]

[edit]: after some self-use, I realised a much easier alternate solution, __pprint_repr__. Rather than attempt to create your own pprint function, simply define the __pprint_repr__ method and return a standard python object. You can group multiple objects inside a dict if you have a complex class.

[edit #2]: I also realised that that it can be useful to have all the _format variables passed to the __pprint_repr__ function, because that allows you to do really cool things -- like show a compacted output if your item is in a list (indent > 0) vs a full output if it's the only object (indent == 0)

This also means this solution is compatibled with Python 2.7, not just Python ~> 3.3

class my_object(object):

    # produce pprint compatible object, easy as pie!
    def __pprint_repr__(self, **kwargs):
        return self.__dict__
    
    # make a multi-level object, easy as cheese-cake!
    def __pprint_repr__(self, **kwargs):
        _indent = kwargs['indent']
        if _indent:
            return self._toText()
        return { self._toText(): self.__dict__ }

    # to take total control (python 3) (requires __repr__ be defined)
    def __pprint__(self, object, stream, indent, allowance, context, level):
        stream.write('my_object(\n')
        self._format(object._data, stream, indent, allowance + 1, context, level)
        stream.write(')')
        pass

The sub-classing is simplicity itself -- tested in Python 3.7 and 2.7:

        if _pprint_repr:
            return PrettyPrinter._format(self, _pprint_repr(object, stream=stream, 
                indent=indent, allowance=allowance, context=context, level=level), 
                    stream, indent, allowance, context, level)

        # else check for alternate _pprint method (if supported ~ python 3.3)
        if getattr(PrettyPrinter, '_dispatch', None):
            _repr = type(object).__repr__
            _pprint = getattr(type(object), '__pprint__', None)
            _exists = self._dispatch.get(_repr, None)
            if not _exists and _pprint:
                self._dispatch[_repr] = _pprint

        return PrettyPrinter._format(self, object, stream, indent, allowance, context, level)
Orwellophile
  • 12,311
  • 3
  • 63
  • 40
  • You should not name your own methods with double underscores ("dunder methods")- this convention denotes that its builtin method that does some transparent "magic". Additionally, the two leading underscores will trigger name mangling unintentionally. – Dillon Davis Sep 09 '21 at 01:19
  • In other words, \_\_this\_\_ denotes that some external force will somehow act upon the attribute and magic will happen. In this case, the external force is PrettyPrinter and the magic is formatted output. Though your rule is sound, this is a "special case". – Orwellophile Sep 22 '21 at 10:28