pprint.py 20.4 KB
Newer Older
1
#  Author:      Fred L. Drake, Jr.
2
#               fdrake@acm.org
3 4 5 6
#
#  This is a simple little module I wrote to make life easier.  I didn't
#  see anything quite like it in the library, though I may have overlooked
#  something.  I wrote this when I was trying to read some heavily nested
7
#  tuples with fairly non-descriptive content.  This is modeled very much
8 9 10 11 12 13 14
#  after Lisp/Scheme - style pretty-printing of lists.  If you find it
#  useful, thank small children who sleep at night.

"""Support to pretty-print lists, tuples, & dictionaries recursively.

Very simple, but useful, especially in debugging data structures.

15 16 17 18 19 20 21
Classes
-------

PrettyPrinter()
    Handle pretty-printing operations onto a stream using a configured
    set of formatting parameters.

22 23 24 25 26 27 28
Functions
---------

pformat()
    Format a Python object into a pretty-printed representation.

pprint()
Skip Montanaro's avatar
Skip Montanaro committed
29
    Pretty-print a Python object to a stream [default is sys.stdout].
30

31 32 33
saferepr()
    Generate a 'standard' repr()-like value, but protect against recursive
    data structures.
34 35 36

"""

37
import collections as _collections
38
import re
39
import sys as _sys
40
import types as _types
41
from io import StringIO as _StringIO
42

43 44
__all__ = ["pprint","pformat","isreadable","isrecursive","saferepr",
           "PrettyPrinter"]
45

46

47 48
def pprint(object, stream=None, indent=1, width=80, depth=None, *,
           compact=False):
Skip Montanaro's avatar
Skip Montanaro committed
49
    """Pretty-print a Python object to a stream [default is sys.stdout]."""
50
    printer = PrettyPrinter(
51 52
        stream=stream, indent=indent, width=width, depth=depth,
        compact=compact)
53 54
    printer.pprint(object)

55
def pformat(object, indent=1, width=80, depth=None, *, compact=False):
56
    """Format a Python object into a pretty-printed representation."""
57 58
    return PrettyPrinter(indent=indent, width=width, depth=depth,
                         compact=compact).pformat(object)
59

60 61
def saferepr(object):
    """Version of repr() which can handle recursive data structures."""
62
    return _safe_repr(object, {}, None, 0)[0]
63

64 65
def isreadable(object):
    """Determine if saferepr(object) is readable by eval()."""
66
    return _safe_repr(object, {}, None, 0)[1]
67 68 69

def isrecursive(object):
    """Determine if object requires a recursive representation."""
70
    return _safe_repr(object, {}, None, 0)[2]
71

72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
class _safe_key:
    """Helper function for key functions when sorting unorderable objects.

    The wrapped-object will fallback to an Py2.x style comparison for
    unorderable types (sorting first comparing the type name and then by
    the obj ids).  Does not work recursively, so dict.items() must have
    _safe_key applied to both the key and the value.

    """

    __slots__ = ['obj']

    def __init__(self, obj):
        self.obj = obj

    def __lt__(self, other):
88
        try:
89
            return self.obj < other.obj
90
        except TypeError:
91 92
            return ((str(type(self.obj)), id(self.obj)) < \
                    (str(type(other.obj)), id(other.obj)))
93 94 95 96 97

def _safe_tuple(t):
    "Helper function for comparing 2-tuples"
    return _safe_key(t[0]), _safe_key(t[1])

98
class PrettyPrinter:
99 100
    def __init__(self, indent=1, width=80, depth=None, stream=None, *,
                 compact=False):
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
        """Handle pretty printing operations onto a stream using a set of
        configured parameters.

        indent
            Number of spaces to indent for each level of nesting.

        width
            Attempted maximum number of columns in the output.

        depth
            The maximum depth to print out nested structures.

        stream
            The desired output stream.  If omitted (or false), the standard
            output stream available at construction will be used.

117 118 119
        compact
            If true, several items will be combined in one line.

120 121 122
        """
        indent = int(indent)
        width = int(width)
123 124 125 126 127 128
        if indent < 0:
            raise ValueError('indent must be >= 0')
        if depth is not None and depth <= 0:
            raise ValueError('depth must be > 0')
        if not width:
            raise ValueError('width must be != 0')
129 130 131
        self._depth = depth
        self._indent_per_level = indent
        self._width = width
132
        if stream is not None:
133
            self._stream = stream
134
        else:
135
            self._stream = _sys.stdout
136
        self._compact = bool(compact)
137 138

    def pprint(self, object):
139 140
        self._format(object, self._stream, 0, 0, {}, 0)
        self._stream.write("\n")
141 142

    def pformat(self, object):
143
        sio = _StringIO()
144
        self._format(object, sio, 0, 0, {}, 0)
145
        return sio.getvalue()
146

147
    def isrecursive(self, object):
148
        return self.format(object, {}, 0, 0)[2]
149 150

    def isreadable(self, object):
151
        s, readable, recursive = self.format(object, {}, 0, 0)
152
        return readable and not recursive
153

154
    def _format(self, object, stream, indent, allowance, context, level):
155
        objid = id(object)
156 157
        if objid in context:
            stream.write(_recursion(object))
158 159
            self._recursive = True
            self._readable = False
160
            return
161
        rep = self._repr(object, context, level)
162
        max_width = self._width - indent - allowance
163 164 165 166 167 168
        if len(rep) > max_width:
            p = self._dispatch.get(type(object).__repr__, None)
            if p is not None:
                context[objid] = 1
                p(self, object, stream, indent, allowance, context, level + 1)
                del context[objid]
169
                return
170 171 172 173 174
            elif isinstance(object, dict):
                context[objid] = 1
                self._pprint_dict(object, stream, indent, allowance,
                                  context, level + 1)
                del context[objid]
175
                return
176
        stream.write(rep)
177

178 179 180 181 182 183 184 185 186
    _dispatch = {}

    def _pprint_dict(self, object, stream, indent, allowance, context, level):
        write = stream.write
        write('{')
        if self._indent_per_level > 1:
            write((self._indent_per_level - 1) * ' ')
        length = len(object)
        if length:
187
            items = sorted(object.items(), key=_safe_tuple)
188 189 190 191 192
            self._format_dict_items(items, stream, indent, allowance + 1,
                                    context, level)
        write('}')

    _dispatch[dict.__repr__] = _pprint_dict
193 194 195 196 197 198 199 200 201 202 203 204 205

    def _pprint_ordered_dict(self, object, stream, indent, allowance, context, level):
        if not len(object):
            stream.write(repr(object))
            return
        cls = object.__class__
        stream.write(cls.__name__ + '(')
        self._format(list(object.items()), stream,
                     indent + len(cls.__name__) + 1, allowance + 1,
                     context, level)
        stream.write(')')

    _dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273

    def _pprint_list(self, object, stream, indent, allowance, context, level):
        stream.write('[')
        self._format_items(object, stream, indent, allowance + 1,
                           context, level)
        stream.write(']')

    _dispatch[list.__repr__] = _pprint_list

    def _pprint_tuple(self, object, stream, indent, allowance, context, level):
        stream.write('(')
        endchar = ',)' if len(object) == 1 else ')'
        self._format_items(object, stream, indent, allowance + len(endchar),
                           context, level)
        stream.write(endchar)

    _dispatch[tuple.__repr__] = _pprint_tuple

    def _pprint_set(self, object, stream, indent, allowance, context, level):
        if not len(object):
            stream.write(repr(object))
            return
        typ = object.__class__
        if typ is set:
            stream.write('{')
            endchar = '}'
        else:
            stream.write(typ.__name__ + '({')
            endchar = '})'
            indent += len(typ.__name__) + 1
        object = sorted(object, key=_safe_key)
        self._format_items(object, stream, indent, allowance + len(endchar),
                           context, level)
        stream.write(endchar)

    _dispatch[set.__repr__] = _pprint_set
    _dispatch[frozenset.__repr__] = _pprint_set

    def _pprint_str(self, object, stream, indent, allowance, context, level):
        write = stream.write
        if not len(object):
            write(repr(object))
            return
        chunks = []
        lines = object.splitlines(True)
        if level == 1:
            indent += 1
            allowance += 1
        max_width1 = max_width = self._width - indent
        for i, line in enumerate(lines):
            rep = repr(line)
            if i == len(lines) - 1:
                max_width1 -= allowance
            if len(rep) <= max_width1:
                chunks.append(rep)
            else:
                # A list of alternating (non-space, space) strings
                parts = re.findall(r'\S*\s*', line)
                assert parts
                assert not parts[-1]
                parts.pop()  # drop empty last part
                max_width2 = max_width
                current = ''
                for j, part in enumerate(parts):
                    candidate = current + part
                    if j == len(parts) - 1 and i == len(lines) - 1:
                        max_width2 -= allowance
                    if len(repr(candidate)) > max_width2:
274 275
                        if current:
                            chunks.append(repr(current))
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
                        current = part
                    else:
                        current = candidate
                if current:
                    chunks.append(repr(current))
        if len(chunks) == 1:
            write(rep)
            return
        if level == 1:
            write('(')
        for i, rep in enumerate(chunks):
            if i > 0:
                write('\n' + ' '*indent)
            write(rep)
        if level == 1:
            write(')')

    _dispatch[str.__repr__] = _pprint_str
294

295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
    def _pprint_bytes(self, object, stream, indent, allowance, context, level):
        write = stream.write
        if len(object) <= 4:
            write(repr(object))
            return
        parens = level == 1
        if parens:
            indent += 1
            allowance += 1
            write('(')
        delim = ''
        for rep in _wrap_bytes_repr(object, self._width - indent, allowance):
            write(delim)
            write(rep)
            if not delim:
                delim = '\n' + ' '*indent
        if parens:
            write(')')

    _dispatch[bytes.__repr__] = _pprint_bytes

    def _pprint_bytearray(self, object, stream, indent, allowance, context, level):
        write = stream.write
        write('bytearray(')
        self._pprint_bytes(bytes(object), stream, indent + 10,
                           allowance + 1, context, level + 1)
        write(')')

    _dispatch[bytearray.__repr__] = _pprint_bytearray

325 326 327 328 329 330 331 332
    def _pprint_mappingproxy(self, object, stream, indent, allowance, context, level):
        stream.write('mappingproxy(')
        self._format(object.copy(), stream, indent + 13, allowance + 1,
                     context, level)
        stream.write(')')

    _dispatch[_types.MappingProxyType.__repr__] = _pprint_mappingproxy

333 334 335
    def _format_dict_items(self, items, stream, indent, allowance, context,
                           level):
        write = stream.write
336
        indent += self._indent_per_level
337 338 339 340 341 342 343 344 345 346 347 348 349
        delimnl = ',\n' + ' ' * indent
        last_index = len(items) - 1
        for i, (key, ent) in enumerate(items):
            last = i == last_index
            rep = self._repr(key, context, level)
            write(rep)
            write(': ')
            self._format(ent, stream, indent + len(rep) + 2,
                         allowance if last else 1,
                         context, level)
            if not last:
                write(delimnl)

350 351
    def _format_items(self, items, stream, indent, allowance, context, level):
        write = stream.write
352 353 354
        indent += self._indent_per_level
        if self._indent_per_level > 1:
            write((self._indent_per_level - 1) * ' ')
355 356
        delimnl = ',\n' + ' ' * indent
        delim = ''
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
        width = max_width = self._width - indent + 1
        it = iter(items)
        try:
            next_ent = next(it)
        except StopIteration:
            return
        last = False
        while not last:
            ent = next_ent
            try:
                next_ent = next(it)
            except StopIteration:
                last = True
                max_width -= allowance
                width -= allowance
372 373
            if self._compact:
                rep = self._repr(ent, context, level)
374
                w = len(rep) + 2
375 376 377 378 379 380 381 382 383 384 385 386
                if width < w:
                    width = max_width
                    if delim:
                        delim = delimnl
                if width >= w:
                    width -= w
                    write(delim)
                    delim = ', '
                    write(rep)
                    continue
            write(delim)
            delim = delimnl
387 388 389
            self._format(ent, stream, indent,
                         allowance if last else 1,
                         context, level)
390

391
    def _repr(self, object, context, level):
392
        repr, readable, recursive = self.format(object, context.copy(),
393
                                                self._depth, level)
394
        if not readable:
395
            self._readable = False
396
        if recursive:
397
            self._recursive = True
398
        return repr
399

400 401 402 403 404 405 406
    def format(self, object, context, maxlevels, level):
        """Format object for a specific context, returning a string
        and flags indicating whether the representation is 'readable'
        and whether the object represents a recursive construct.
        """
        return _safe_repr(object, context, maxlevels, level)

407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
    def _pprint_default_dict(self, object, stream, indent, allowance, context, level):
        if not len(object):
            stream.write(repr(object))
            return
        rdf = self._repr(object.default_factory, context, level)
        cls = object.__class__
        indent += len(cls.__name__) + 1
        stream.write('%s(%s,\n%s' % (cls.__name__, rdf, ' ' * indent))
        self._pprint_dict(object, stream, indent, allowance + 1, context, level)
        stream.write(')')

    _dispatch[_collections.defaultdict.__repr__] = _pprint_default_dict

    def _pprint_counter(self, object, stream, indent, allowance, context, level):
        if not len(object):
            stream.write(repr(object))
            return
        cls = object.__class__
        stream.write(cls.__name__ + '({')
        if self._indent_per_level > 1:
            stream.write((self._indent_per_level - 1) * ' ')
        items = object.most_common()
        self._format_dict_items(items, stream,
                                indent + len(cls.__name__) + 1, allowance + 2,
                                context, level)
        stream.write('})')

    _dispatch[_collections.Counter.__repr__] = _pprint_counter

    def _pprint_chain_map(self, object, stream, indent, allowance, context, level):
        if not len(object.maps):
            stream.write(repr(object))
            return
        cls = object.__class__
        stream.write(cls.__name__ + '(')
        indent += len(cls.__name__) + 1
        for i, m in enumerate(object.maps):
            if i == len(object.maps) - 1:
                self._format(m, stream, indent, allowance + 1, context, level)
                stream.write(')')
            else:
                self._format(m, stream, indent, 1, context, level)
                stream.write(',\n' + ' ' * indent)

    _dispatch[_collections.ChainMap.__repr__] = _pprint_chain_map

    def _pprint_deque(self, object, stream, indent, allowance, context, level):
        if not len(object):
            stream.write(repr(object))
            return
        cls = object.__class__
        stream.write(cls.__name__ + '(')
        indent += len(cls.__name__) + 1
        stream.write('[')
        if object.maxlen is None:
            self._format_items(object, stream, indent, allowance + 2,
                               context, level)
            stream.write('])')
        else:
            self._format_items(object, stream, indent, 2,
                               context, level)
            rml = self._repr(object.maxlen, context, level)
            stream.write('],\n%smaxlen=%s)' % (' ' * indent, rml))

    _dispatch[_collections.deque.__repr__] = _pprint_deque

    def _pprint_user_dict(self, object, stream, indent, allowance, context, level):
        self._format(object.data, stream, indent, allowance, context, level - 1)

    _dispatch[_collections.UserDict.__repr__] = _pprint_user_dict

    def _pprint_user_list(self, object, stream, indent, allowance, context, level):
        self._format(object.data, stream, indent, allowance, context, level - 1)

    _dispatch[_collections.UserList.__repr__] = _pprint_user_list

    def _pprint_user_string(self, object, stream, indent, allowance, context, level):
        self._format(object.data, stream, indent, allowance, context, level - 1)

    _dispatch[_collections.UserString.__repr__] = _pprint_user_string
487

488
# Return triple (repr_string, isreadable, isrecursive).
489

490
def _safe_repr(object, context, maxlevels, level):
491
    typ = type(object)
492 493
    if typ in _builtin_scalars:
        return repr(object), True, False
494

495
    r = getattr(typ, "__repr__", None)
496
    if issubclass(typ, dict) and r is dict.__repr__:
497
        if not object:
498
            return "{}", True, False
499
        objid = id(object)
500
        if maxlevels and level >= maxlevels:
501
            return "{...}", False, objid in context
502
        if objid in context:
503
            return _recursion(object), False, True
504
        context[objid] = 1
505 506
        readable = True
        recursive = False
507
        components = []
508 509 510
        append = components.append
        level += 1
        saferepr = _safe_repr
511
        items = sorted(object.items(), key=_safe_tuple)
512
        for k, v in items:
513 514 515
            krepr, kreadable, krecur = saferepr(k, context, maxlevels, level)
            vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level)
            append("%s: %s" % (krepr, vrepr))
516
            readable = readable and kreadable and vreadable
517
            if krecur or vrecur:
518
                recursive = True
519
        del context[objid]
520
        return "{%s}" % ", ".join(components), readable, recursive
521

522 523 524
    if (issubclass(typ, list) and r is list.__repr__) or \
       (issubclass(typ, tuple) and r is tuple.__repr__):
        if issubclass(typ, list):
525
            if not object:
526
                return "[]", True, False
527
            format = "[%s]"
528
        elif len(object) == 1:
529 530 531
            format = "(%s,)"
        else:
            if not object:
532
                return "()", True, False
533
            format = "(%s)"
534
        objid = id(object)
535
        if maxlevels and level >= maxlevels:
536
            return format % "...", False, objid in context
537
        if objid in context:
538
            return _recursion(object), False, True
539
        context[objid] = 1
540 541
        readable = True
        recursive = False
542
        components = []
543 544 545 546 547 548
        append = components.append
        level += 1
        for o in object:
            orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level)
            append(orepr)
            if not oreadable:
549
                readable = False
550
            if orecur:
551
                recursive = True
552
        del context[objid]
553
        return format % ", ".join(components), readable, recursive
Tim Peters's avatar
Tim Peters committed
554

555
    rep = repr(object)
556
    return rep, (rep and not rep.startswith('<')), False
557

558 559
_builtin_scalars = frozenset({str, bytes, bytearray, int, float, complex,
                              bool, type(None)})
560 561 562

def _recursion(object):
    return ("<Recursion on %s with id=%s>"
563
            % (type(object).__name__, id(object)))
564 565 566 567 568 569 570 571 572 573 574 575


def _perfcheck(object=None):
    import time
    if object is None:
        object = [("string", (1, 2), [3, 4], {5: 6, 7: 8})] * 100000
    p = PrettyPrinter()
    t1 = time.time()
    _safe_repr(object, {}, None, 0)
    t2 = time.time()
    p.pformat(object)
    t3 = time.time()
576 577
    print("_safe_repr:", t2 - t1)
    print("pformat:", t3 - t2)
578

579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
def _wrap_bytes_repr(object, width, allowance):
    current = b''
    last = len(object) // 4 * 4
    for i in range(0, len(object), 4):
        part = object[i: i+4]
        candidate = current + part
        if i == last:
            width -= allowance
        if len(repr(candidate)) > width:
            if current:
                yield repr(current)
            current = part
        else:
            current = candidate
    if current:
        yield repr(current)

596 597
if __name__ == "__main__":
    _perfcheck()