Format specifier for pretty printing

Recently I found myself debugging some stuff in nested data structures and noticed that pretty-printing (pp / pprint) does not provide much help there compared to a normal print.

Since python has format specifiers, I was wondering whether these could be use to add pretty printing functionality to any object.
The pprint module could then make use of this, by delegating the formatting to the objects themselves with a fallback to the current implementation (eg the object.__format__ could house the current pprint implementation for the specifier)

Additionally, this could allow easier use of pretty formatted object representations in f strings.

This would also help pprinting dataclasses, or even third-party driven classes like the ones based on Pydantic’s or SQLAlchemy’s BaseModel.

Let me know what you think about this.

Could you give an example of how this would be called, and what the output would look like?

1 Like

Sure thing. In the example below I’m using p as the format specifier for pretty printing:

from pprint import pprint


class Bar:
    def __init__(self, y):
        self.y = y

    def __format__(self, format_spec):
        if format_spec == "p":
            return f"Bar(y={self.y})"
        return super().__format__(format_spec)


class Foo:
    def __init__(self, x, bars):
        self.x = x
        self.bars = bars

    def __format__(self, format_spec):
        if format_spec == "p":
            return f"""Foo(x={self.x}, bars=[
                {',\n                '.join((format(b, "p") for b in self.bars))}
                 ])"""
        return super().__format__(format_spec)


foo = Foo(3, [Bar(i) for i in range(10)])

pprint(foo) # if pprint would use the format specifier the output would be as the one 3 lines below
#<__main__.Foo object at 0x7907899c1550>

print(f"{foo:p}")
# Foo(x=3, bars=[
#                 Bar(y=0),
#                 Bar(y=1),
#                 Bar(y=2),
#                 Bar(y=3),
#                 Bar(y=4),
#                 Bar(y=5),
#                 Bar(y=6),
#                 Bar(y=7),
#                 Bar(y=8),
#                 Bar(y=9)
#                  ])

I always define a suitable __repr__ method on my classes to support my debugging.

How would a __format__ method be any different?

I always define a suitable __repr__ method on my classes to support my debugging.

How would a __format__ method be any different?

That’s actually a very good question. I guess my thinking was more along the lines of allowing the pprint module to delegate the formatting and to add other classes to it.

I honestly haven’t thought about using __repr__ for that scenario :person_facepalming: .


I guess this format specifier won’t really provide any new value over just using __repr__.

For my personal use a styling rule that I follow is that when an expression is going to be split into multiple lines, the parentheses/brackets/braces that is the outermost is the one that gets split into multiple lines.

In my case, I would do

Foo(
    x=3,
    bars=[Bar(y=0), Bar(y=1), Bar(y=2)]
)

If that line 3 were still too long, then it would be the bracket’s turn to be split, and so on following the nesting order and the maximum line width required.

Foo(
    x=3,
    bars=[
        Bar(y=0),
        Bar(y=1),
        Bar(y=2)
    ]
)
1 Like