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

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

44 45 46 47 48 49 50
# cache these for faster access:
_commajoin = ", ".join
_id = id
_len = len
_type = type


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

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

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

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

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

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

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

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

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

        """
        indent = int(indent)
        width = int(width)
124
        assert indent >= 0, "indent must be >= 0"
125
        assert depth is None or depth > 0, "depth must be > 0"
126
        assert width, "width must be != 0"
127 128 129
        self._depth = depth
        self._indent_per_level = indent
        self._width = width
130
        if stream is not None:
131
            self._stream = stream
132
        else:
133
            self._stream = _sys.stdout
134 135

    def pprint(self, object):
136 137
        self._format(object, self._stream, 0, 0, {}, 0)
        self._stream.write("\n")
138 139

    def pformat(self, object):
140
        sio = _StringIO()
141
        self._format(object, sio, 0, 0, {}, 0)
142
        return sio.getvalue()
143

144
    def isrecursive(self, object):
145
        return self.format(object, {}, 0, 0)[2]
146 147

    def isreadable(self, object):
148
        s, readable, recursive = self.format(object, {}, 0, 0)
149
        return readable and not recursive
150

151
    def _format(self, object, stream, indent, allowance, context, level):
152
        level = level + 1
153 154 155
        objid = _id(object)
        if objid in context:
            stream.write(_recursion(object))
156 157
            self._recursive = True
            self._readable = False
158
            return
159
        rep = self._repr(object, context, level - 1)
160
        typ = _type(object)
161
        sepLines = _len(rep) > (self._width - 1 - indent - allowance)
162 163
        write = stream.write

164 165 166 167
        if self._depth and level > self._depth:
            write(rep)
            return

168
        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 175 176
                length = _len(object)
                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
                elif issubclass(typ, set):
209 210 211
                    if not length:
                        write('set()')
                        return
212 213
                    write('{')
                    endchar = '}'
214
                    object = sorted(object, key=_safe_key)
215
                elif issubclass(typ, frozenset):
216 217 218
                    if not length:
                        write('frozenset()')
                        return
219 220
                    write('frozenset({')
                    endchar = '})'
221
                    object = sorted(object, key=_safe_key)
222
                    indent += 10
223 224 225
                else:
                    write('(')
                    endchar = ')'
226 227
                if self._indent_per_level > 1:
                    write((self._indent_per_level - 1) * ' ')
228 229
                if length:
                    context[objid] = 1
230 231 232
                    indent = indent + self._indent_per_level
                    self._format(object[0], stream, indent, allowance + 1,
                                 context, level)
233 234 235
                    if length > 1:
                        for ent in object[1:]:
                            write(',\n' + ' '*indent)
236
                            self._format(ent, stream, indent,
237
                                          allowance + 1, context, level)
238
                    indent = indent - self._indent_per_level
239
                    del context[objid]
240
                if issubclass(typ, tuple) and length == 1:
241 242 243 244 245
                    write(',')
                write(endchar)
                return

        write(rep)
246

247
    def _repr(self, object, context, level):
248
        repr, readable, recursive = self.format(object, context.copy(),
249
                                                self._depth, level)
250
        if not readable:
251
            self._readable = False
252
        if recursive:
253
            self._recursive = True
254
        return repr
255

256 257 258 259 260 261 262 263
    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)


264
# Return triple (repr_string, isreadable, isrecursive).
265

266 267
def _safe_repr(object, context, maxlevels, level):
    typ = _type(object)
268
    if typ is str:
269
        if 'locale' not in _sys.modules:
270
            return repr(object), True, False
271 272 273 274 275 276
        if "'" in object and '"' not in object:
            closure = '"'
            quotes = {'"': '\\"'}
        else:
            closure = "'"
            quotes = {"'": "\\'"}
277
        qget = quotes.get
278
        sio = _StringIO()
279
        write = sio.write
280 281
        for char in object:
            if char.isalpha():
282
                write(char)
283
            else:
284
                write(qget(char, repr(char)[1:-1]))
285
        return ("%s%s%s" % (closure, sio.getvalue(), closure)), True, False
286

287
    r = getattr(typ, "__repr__", None)
288
    if issubclass(typ, dict) and r is dict.__repr__:
289
        if not object:
290
            return "{}", True, False
291
        objid = _id(object)
292
        if maxlevels and level >= maxlevels:
293
            return "{...}", False, objid in context
294
        if objid in context:
295
            return _recursion(object), False, True
296
        context[objid] = 1
297 298
        readable = True
        recursive = False
299
        components = []
300 301 302
        append = components.append
        level += 1
        saferepr = _safe_repr
303
        items = sorted(object.items(), key=_safe_tuple)
304
        for k, v in items:
305 306 307
            krepr, kreadable, krecur = saferepr(k, context, maxlevels, level)
            vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level)
            append("%s: %s" % (krepr, vrepr))
308
            readable = readable and kreadable and vreadable
309
            if krecur or vrecur:
310
                recursive = True
311 312 313
        del context[objid]
        return "{%s}" % _commajoin(components), readable, recursive

314 315 316
    if (issubclass(typ, list) and r is list.__repr__) or \
       (issubclass(typ, tuple) and r is tuple.__repr__):
        if issubclass(typ, list):
317
            if not object:
318
                return "[]", True, False
319 320 321 322 323
            format = "[%s]"
        elif _len(object) == 1:
            format = "(%s,)"
        else:
            if not object:
324
                return "()", True, False
325 326
            format = "(%s)"
        objid = _id(object)
327
        if maxlevels and level >= maxlevels:
328
            return format % "...", False, objid in context
329
        if objid in context:
330
            return _recursion(object), False, True
331
        context[objid] = 1
332 333
        readable = True
        recursive = False
334
        components = []
335 336 337 338 339 340
        append = components.append
        level += 1
        for o in object:
            orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level)
            append(orepr)
            if not oreadable:
341
                readable = False
342
            if orecur:
343
                recursive = True
344 345
        del context[objid]
        return format % _commajoin(components), readable, recursive
Tim Peters's avatar
Tim Peters committed
346

347
    rep = repr(object)
348
    return rep, (rep and not rep.startswith('<')), False
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365


def _recursion(object):
    return ("<Recursion on %s with id=%s>"
            % (_type(object).__name__, _id(object)))


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()
366 367
    print("_safe_repr:", t2 - t1)
    print("pformat:", t3 - t2)
368 369 370

if __name__ == "__main__":
    _perfcheck()