pprint.py 14.5 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 re
38
import sys as _sys
39
from collections import OrderedDict as _OrderedDict
40
from io import StringIO as _StringIO
41

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

45

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

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

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

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

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

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
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):
87 88 89 90 91
        try:
            rv = self.obj.__lt__(other.obj)
        except TypeError:
            rv = NotImplemented

92
        if rv is NotImplemented:
93 94
            rv = (str(type(self.obj)), id(self.obj)) < \
                 (str(type(other.obj)), id(other.obj))
95 96 97 98 99 100
        return rv

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

101
class PrettyPrinter:
102 103
    def __init__(self, indent=1, width=80, depth=None, stream=None, *,
                 compact=False):
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
        """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.

120 121 122
        compact
            If true, several items will be combined in one line.

123 124 125
        """
        indent = int(indent)
        width = int(width)
126
        assert indent >= 0, "indent must be >= 0"
127
        assert depth is None or depth > 0, "depth must be > 0"
128
        assert width, "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
        level = level + 1
156
        objid = id(object)
157 158
        if objid in context:
            stream.write(_recursion(object))
159 160
            self._recursive = True
            self._readable = False
161
            return
162
        rep = self._repr(object, context, level - 1)
163
        typ = type(object)
164
        max_width = self._width - 1 - indent - allowance
165
        sepLines = len(rep) > max_width
166 167 168
        write = stream.write

        if sepLines:
169
            r = getattr(typ, "__repr__", None)
170
            if issubclass(typ, dict):
171
                write('{')
172 173
                if self._indent_per_level > 1:
                    write((self._indent_per_level - 1) * ' ')
174
                length = len(object)
175 176
                if length:
                    context[objid] = 1
177
                    indent = indent + self._indent_per_level
178 179 180 181
                    if issubclass(typ, _OrderedDict):
                        items = list(object.items())
                    else:
                        items = sorted(object.items(), key=_safe_tuple)
182
                    key, ent = items[0]
183
                    rep = self._repr(key, context, level)
184 185
                    write(rep)
                    write(': ')
186
                    self._format(ent, stream, indent + len(rep) + 2,
187 188 189
                                  allowance + 1, context, level)
                    if length > 1:
                        for key, ent in items[1:]:
190
                            rep = self._repr(key, context, level)
191
                            write(',\n%s%s: ' % (' '*indent, rep))
192
                            self._format(ent, stream, indent + len(rep) + 2,
193
                                          allowance + 1, context, level)
194
                    indent = indent - self._indent_per_level
195 196 197 198
                    del context[objid]
                write('}')
                return

199 200 201 202 203
            if ((issubclass(typ, list) and r is list.__repr__) or
                (issubclass(typ, tuple) and r is tuple.__repr__) or
                (issubclass(typ, set) and r is set.__repr__) or
                (issubclass(typ, frozenset) and r is frozenset.__repr__)
               ):
204
                length = len(object)
205
                if issubclass(typ, list):
206 207
                    write('[')
                    endchar = ']'
208 209 210 211
                elif issubclass(typ, tuple):
                    write('(')
                    endchar = ')'
                else:
212
                    if not length:
213
                        write(rep)
214
                        return
215 216 217 218 219 220 221
                    if typ is set:
                        write('{')
                        endchar = '}'
                    else:
                        write(typ.__name__)
                        write('({')
                        endchar = '})'
222
                        indent += len(typ.__name__) + 1
223
                    object = sorted(object, key=_safe_key)
224 225
                if self._indent_per_level > 1:
                    write((self._indent_per_level - 1) * ' ')
226 227
                if length:
                    context[objid] = 1
228 229 230
                    self._format_items(object, stream,
                                       indent + self._indent_per_level,
                                       allowance + 1, context, level)
231
                    del context[objid]
232
                if issubclass(typ, tuple) and length == 1:
233 234 235 236
                    write(',')
                write(endchar)
                return

237
            if issubclass(typ, str) and len(object) > 0 and r is str.__repr__:
238 239 240 241 242 243 244 245
                def _str_parts(s):
                    """
                    Return a list of string literals comprising the repr()
                    of the given string using literal concatenation.
                    """
                    lines = s.splitlines(True)
                    for i, line in enumerate(lines):
                        rep = repr(line)
246
                        if len(rep) <= max_width:
247 248 249 250 251
                            yield rep
                        else:
                            # A list of alternating (non-space, space) strings
                            parts = re.split(r'(\s+)', line) + ['']
                            current = ''
252
                            for i in range(0, len(parts), 2):
253 254
                                part = parts[i] + parts[i+1]
                                candidate = current + part
255
                                if len(repr(candidate)) > max_width:
256 257 258 259 260 261 262 263 264 265 266 267
                                    if current:
                                        yield repr(current)
                                    current = part
                                else:
                                    current = candidate
                            if current:
                                yield repr(current)
                for i, rep in enumerate(_str_parts(object)):
                    if i > 0:
                        write('\n' + ' '*indent)
                    write(rep)
                return
268
        write(rep)
269

270 271 272 273 274 275 276 277
    def _format_items(self, items, stream, indent, allowance, context, level):
        write = stream.write
        delimnl = ',\n' + ' ' * indent
        delim = ''
        width = max_width = self._width - indent - allowance + 2
        for ent in items:
            if self._compact:
                rep = self._repr(ent, context, level)
278
                w = len(rep) + 2
279 280 281 282 283 284 285 286 287 288 289 290 291 292
                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
            self._format(ent, stream, indent, allowance, context, level)

293
    def _repr(self, object, context, level):
294
        repr, readable, recursive = self.format(object, context.copy(),
295
                                                self._depth, level)
296
        if not readable:
297
            self._readable = False
298
        if recursive:
299
            self._recursive = True
300
        return repr
301

302 303 304 305 306 307 308 309
    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)


310
# Return triple (repr_string, isreadable, isrecursive).
311

312
def _safe_repr(object, context, maxlevels, level):
313
    typ = type(object)
314
    if typ is str:
315
        if 'locale' not in _sys.modules:
316
            return repr(object), True, False
317 318 319 320 321 322
        if "'" in object and '"' not in object:
            closure = '"'
            quotes = {'"': '\\"'}
        else:
            closure = "'"
            quotes = {"'": "\\'"}
323
        qget = quotes.get
324
        sio = _StringIO()
325
        write = sio.write
326 327
        for char in object:
            if char.isalpha():
328
                write(char)
329
            else:
330
                write(qget(char, repr(char)[1:-1]))
331
        return ("%s%s%s" % (closure, sio.getvalue(), closure)), True, False
332

333
    r = getattr(typ, "__repr__", None)
334
    if issubclass(typ, dict) and r is dict.__repr__:
335
        if not object:
336
            return "{}", True, False
337
        objid = id(object)
338
        if maxlevels and level >= maxlevels:
339
            return "{...}", False, objid in context
340
        if objid in context:
341
            return _recursion(object), False, True
342
        context[objid] = 1
343 344
        readable = True
        recursive = False
345
        components = []
346 347 348
        append = components.append
        level += 1
        saferepr = _safe_repr
349
        items = sorted(object.items(), key=_safe_tuple)
350
        for k, v in items:
351 352 353
            krepr, kreadable, krecur = saferepr(k, context, maxlevels, level)
            vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level)
            append("%s: %s" % (krepr, vrepr))
354
            readable = readable and kreadable and vreadable
355
            if krecur or vrecur:
356
                recursive = True
357
        del context[objid]
358
        return "{%s}" % ", ".join(components), readable, recursive
359

360 361 362
    if (issubclass(typ, list) and r is list.__repr__) or \
       (issubclass(typ, tuple) and r is tuple.__repr__):
        if issubclass(typ, list):
363
            if not object:
364
                return "[]", True, False
365
            format = "[%s]"
366
        elif len(object) == 1:
367 368 369
            format = "(%s,)"
        else:
            if not object:
370
                return "()", True, False
371
            format = "(%s)"
372
        objid = id(object)
373
        if maxlevels and level >= maxlevels:
374
            return format % "...", False, objid in context
375
        if objid in context:
376
            return _recursion(object), False, True
377
        context[objid] = 1
378 379
        readable = True
        recursive = False
380
        components = []
381 382 383 384 385 386
        append = components.append
        level += 1
        for o in object:
            orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level)
            append(orepr)
            if not oreadable:
387
                readable = False
388
            if orecur:
389
                recursive = True
390
        del context[objid]
391
        return format % ", ".join(components), readable, recursive
Tim Peters's avatar
Tim Peters committed
392

393
    rep = repr(object)
394
    return rep, (rep and not rep.startswith('<')), False
395 396 397 398


def _recursion(object):
    return ("<Recursion on %s with id=%s>"
399
            % (type(object).__name__, id(object)))
400 401 402 403 404 405 406 407 408 409 410 411


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()
412 413
    print("_safe_repr:", t2 - t1)
    print("pformat:", t3 - t2)
414 415 416

if __name__ == "__main__":
    _perfcheck()