How can I make my class pretty printable in Python?
Asked Answered
E

4

22

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.

Extravehicular answered 27/11, 2016 at 11:2 Comment(4)
What do you mean by 'pretty printed' class? Like dict?Happ
pprint(config) gives only <lib.ExtendedConfigParser.ExtendedConfigParser object at 0x0000000003569940>. 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.Extravehicular
I don't think pprint offers that functionality. However, you could give your class a __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.Ignazio
Related: #3258572Violoncellist
F
16

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.

Franko answered 27/11, 2016 at 11:9 Comment(9)
Your proposed solution seems to have no harm if my classes already use (multi-)inheritance, right?Extravehicular
@Paebbels: do make sure there is a unique __repr__ method to key to, otherwise I see no potential for harm.Franko
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.Extravehicular
@Extravehicular you'll have to experiment with how you'd integrate that with the pprint output (given the indentation and allowance values, for example).Franko
@RickGraves: it’s still there, even in trunk: github.com/python/cpython/blob/…, but I had a small error in the code, now corrected. Python 2 never had it.Franko
Sorry, my first comment (deleted) was incorrect. Python 3.6, 3.7 & 3.8 have _dispatch, 2.7 does not.Pellicle
@RickGraves: indeed, as I said, Python 2 never supported this.Franko
@RickGraves the table was introduced in 3.5.Franko
Any idea why there isn't a special dunder method associated with pretty-printing?Cristoforo
R
2

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

pprint(obj.dict())
Richma answered 2/3, 2020 at 11:6 Comment(1)
This doesn't help if you have nested instances of non-standard dicts.Jeffreyjeffreys
P
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 )
Pellicle answered 12/1, 2020 at 23:31 Comment(0)
H
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)
Hypoderm answered 17/2, 2021 at 21:37 Comment(8)
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.Finite
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".Hypoderm
@DillonDavis the 2 leading underscores will not trigger name mangling as name mangling only triggers when there aren't also 2 trailing underscores, see docs.python.org/3/reference/…Cristoforo
@Cristoforo 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.Finite
@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.Cristoforo
@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.Cristoforo
@Cristoforo 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.Hypoderm
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.Cristoforo

© 2022 - 2024 — McMap. All rights reserved.