21

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
  • 15,573
  • 13
  • 70
  • 139
  • 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 OurBigBook.com Sep 03 '20 at 14:11

4 Answers4

14

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.

You may want to look at rich, a third-party library that has some great pretty-printing capabilities, and supports hooks (__rich_repr__); see the documentation on customising pretty-printing.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 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
  • Any idea why there isn't a special dunder method associated with pretty-printing? – MarcellPerger Jun 28 '23 at 18:17
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,775
  • 3
  • 27
  • 52
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
  • 517
  • 5
  • 11
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
  • 13,235
  • 3
  • 69
  • 45
  • 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
  • @DillonDavis the 2 leading underscores will **not** trigger name mangling as name mangling only triggers when there aren't also 2 trailing underscores, see https://docs.python.org/3/reference/expressions.html#private-name-mangling – MarcellPerger Jun 28 '23 at 18:14
  • @MarcellPerger that is not true. If you read the docs you've cited, "For example, the identifier __spam occurring in a class named Ham will be transformed to _Ham__spam.". Builtin magic / "dunder" methods have double underscores on both sides, but leading double underscores trigger name mangling. – Dillon Davis Jun 29 '23 at 07:32
  • @DillonDavis "When an identifier that textually occurs in a class definition begins with two or more underscore characters **and does not end in two or more underscores**". There also has to be **no** 2 trailing underscores (as well as the 2 leading underscores) for it to be mangled. But dunder methods have 2 trailing as well as 2 leading underscores so don't get mangled. – MarcellPerger Jun 29 '23 at 14:19
  • @DillonDavis Also, if you run `class X: __my_dunder__ = 8 print(X.__my_dunder__)` you get an output of 8 and it doesn't give an `AttributeError` meaning that the `__my_dunder__` attribute does exist and therefore was not mangled. – MarcellPerger Jun 29 '23 at 14:23
  • @MarcellPerger DillonDavis is correct, just as `_function` will not be imported from a module, `__method` will not be usable outside a class. That's what it says in the link you references, and since I have been using the code above for many years, I can assure you it works very well. – Orwellophile Jul 06 '23 at 05:23
  • Yes, `__method` will be mangled so can't be used outside of the class. `__method__` (note the 2 underscores at the end) will not be and `__method__` can be used outside of the class. – MarcellPerger Jul 06 '23 at 10:22