32

I tried pprint from pprint, but its output is just one line, there is no multiline output and no indentation.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
bobzhang
  • 1,771
  • 3
  • 17
  • 19
  • 4
    Can you give an example of the object you want to print and how you want the printed output to appear? – TigerhawkT3 May 05 '15 at 20:13
  • 1
    What were you expecting? If you need more control over how it prints out, create a custom object and define `__repr__`. – jonrsharpe May 05 '15 at 20:14

3 Answers3

23

I use namedtuple's _asdict method.

However, it returns an OrderedDict which pprint won't indent, so I convert it to a dict:

>>> from collections import namedtuple

>>> Busbar = namedtuple('Busbar', 'id name voltage')
>>> busbar = Busbar(id=102, name='FACTORY', voltage=21.8)

With pprint and dict:

>>> from pprint import pprint
>>> pprint(dict(busbar._asdict()))
{'id': 102,
 'name': 'FACTORY',
 'voltage': 21.8}
Peter Wood
  • 23,859
  • 5
  • 60
  • 99
  • 1
    @KFL it wouldn't work, no. You'd have to write a little utility function. – Peter Wood Oct 23 '18 at 22:13
  • 1
    As of Python 3.7--3.10, applying `vars` to a named tuple raises `TypeError: vars() argument must have __dict__ attribute`. The earlier version using the method `_as_dict` works. – 0 _ Oct 27 '21 at 14:57
  • 1
    @IoannisFilippidis thanks, I've rolled back to a previous version of the answer which uses `_as_dict`. – Peter Wood Oct 27 '21 at 18:06
16

The pprint PrettyPrinter in Python 3 is much more extendable than it used to be in Python 2. You could create your own printer like below to add methods for the object you want to handle without messing too much with pprint "private" methods and attributes.

You can see an online example here: https://repl.it/HkDd/1

from io import StringIO
import pprint

class MyPrettyPrinter(pprint.PrettyPrinter):
    def format_namedtuple(self, object, stream, indent, allowance, context, level):
        # Code almost equal to _format_dict, see pprint code
        write = stream.write
        write(object.__class__.__name__ + '(')
        object_dict = object._asdict()
        length = len(object_dict)
        if length:
            # We first try to print inline, and if it is too large then we print it on multiple lines
            inline_stream = StringIO()
            self.format_namedtuple_items(object_dict.items(), inline_stream, indent, allowance + 1, context, level, inline=True)
            max_width = self._width - indent - allowance
            if len(inline_stream.getvalue()) > max_width:
                self.format_namedtuple_items(object_dict.items(), stream, indent, allowance + 1, context, level, inline=False)
            else:
                stream.write(inline_stream.getvalue())
        write(')')

    def format_namedtuple_items(self, items, stream, indent, allowance, context, level, inline=False):
        # Code almost equal to _format_dict_items, see pprint code
        indent += self._indent_per_level
        write = stream.write
        last_index = len(items) - 1
        if inline:
            delimnl = ', '
        else:
            delimnl = ',\n' + ' ' * indent
            write('\n' + ' ' * indent)
        for i, (key, ent) in enumerate(items):
            last = i == last_index
            write(key + '=')
            self._format(ent, stream, indent + len(key) + 2,
                         allowance if last else 1,
                         context, level)
            if not last:
                write(delimnl)

    def _format(self, object, stream, indent, allowance, context, level):
        # We dynamically add the types of our namedtuple and namedtuple like 
        # classes to the _dispatch object of pprint that maps classes to
        # formatting methods
        # We use a simple criteria (_asdict method) that allows us to use the
        # same formatting on other classes but a more precise one is possible
        if hasattr(object, '_asdict') and type(object).__repr__ not in self._dispatch:
            self._dispatch[type(object).__repr__] = MyPrettyPrinter.format_namedtuple
        super()._format(object, stream, indent, allowance, context, level)

and use it like so:

from collections import namedtuple

Segment = namedtuple('Segment', 'p1 p2')
# Your own namedtuple-like class
class Node:
    def __init__(self, x, y, segments=[]):
        self.x = x
        self.y = y
        self.segments = segments

    def _asdict(self):
        return {"x": self.x, "y": self.y, "segments": self.segments}

    # Default repr
    def __repr__(self):
        return "Node(x={}, y={}, segments={})".format(self.x, self.y, self.segments)

# A circular structure for the demo
node = Node(0, 0)
segments = [
    Segment(node, Node(1, 1)),
    Segment(node, Node(2, 1)),
    Segment(node, Node(1, 2, segments=[
      Segment(Node(2, 3), Node(1, 1)),
    ])),
]
node.segments = segments

pp = MyPrettyPrinter(indent=2, depth=2)
pp.pprint(node)

outputs

Node(
  x=0,
  y=0,
  segments=[ Segment(
                p1=<Recursion on Node with id=139778851454536>,
                p2=Node(x=1, y=1, segments=[])),
              Segment(
                p1=<Recursion on Node with id=139778851454536>,
                p2=Node(x=2, y=1, segments=[])),
              Segment(
                p1=<Recursion on Node with id=139778851454536>,
                p2=Node(x=1, y=2, segments=[...]))])
Perceval W
  • 448
  • 6
  • 11
4

Unlike all other solutions here, this solution is generic and works for namedtuples inside other containers as well:

import black

# value_to_print can either be a namedtuple, or a container containing tuples,
# or a namedtuple containing containers containing other namedtuples,
# or whatever else you want.
print(black.format_str(repr(value_to_print), mode=black.Mode()))

This requires installing black, which can be done via pip install black.

tohava
  • 5,344
  • 1
  • 25
  • 47
  • I would not recommend using `sudo` for installing python packages - this may cause problems with system packages and breaks any virtualenvs. Just use `pip install..` - there are not many instances (I'd say none but there may be some special case) in which you'd want to use `sudo pip install..` – Jan Spurny Nov 05 '21 at 20:30
  • @JanSpurny - Thanks, I have removed the sudo – tohava Nov 07 '21 at 18:06