• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#  Author:      Fred L. Drake, Jr.
2#               fdrake@acm.org
3#
4#  This is a simple little module I wrote to make life easier.  I didn't
5#  see anything quite like it in the library, though I may have overlooked
6#  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#  after Lisp/Scheme - style pretty-printing of lists.  If you find it
9#  useful, thank small children who sleep at night.
10
11"""Support to pretty-print lists, tuples, & dictionaries recursively.
12
13Very simple, but useful, especially in debugging data structures.
14
15Classes
16-------
17
18PrettyPrinter()
19    Handle pretty-printing operations onto a stream using a configured
20    set of formatting parameters.
21
22Functions
23---------
24
25pformat()
26    Format a Python object into a pretty-printed representation.
27
28pprint()
29    Pretty-print a Python object to a stream [default is sys.stdout].
30
31saferepr()
32    Generate a 'standard' repr()-like value, but protect against recursive
33    data structures.
34
35"""
36
37import sys as _sys
38import warnings
39
40from cStringIO import StringIO as _StringIO
41
42__all__ = ["pprint","pformat","isreadable","isrecursive","saferepr",
43           "PrettyPrinter"]
44
45# cache these for faster access:
46_commajoin = ", ".join
47_id = id
48_len = len
49_type = type
50
51
52def pprint(object, stream=None, indent=1, width=80, depth=None):
53    """Pretty-print a Python object to a stream [default is sys.stdout]."""
54    printer = PrettyPrinter(
55        stream=stream, indent=indent, width=width, depth=depth)
56    printer.pprint(object)
57
58def pformat(object, indent=1, width=80, depth=None):
59    """Format a Python object into a pretty-printed representation."""
60    return PrettyPrinter(indent=indent, width=width, depth=depth).pformat(object)
61
62def saferepr(object):
63    """Version of repr() which can handle recursive data structures."""
64    return _safe_repr(object, {}, None, 0)[0]
65
66def isreadable(object):
67    """Determine if saferepr(object) is readable by eval()."""
68    return _safe_repr(object, {}, None, 0)[1]
69
70def isrecursive(object):
71    """Determine if object requires a recursive representation."""
72    return _safe_repr(object, {}, None, 0)[2]
73
74def _sorted(iterable):
75    with warnings.catch_warnings():
76        if _sys.py3kwarning:
77            warnings.filterwarnings("ignore", "comparing unequal types "
78                                    "not supported", DeprecationWarning)
79        return sorted(iterable)
80
81class PrettyPrinter:
82    def __init__(self, indent=1, width=80, depth=None, stream=None):
83        """Handle pretty printing operations onto a stream using a set of
84        configured parameters.
85
86        indent
87            Number of spaces to indent for each level of nesting.
88
89        width
90            Attempted maximum number of columns in the output.
91
92        depth
93            The maximum depth to print out nested structures.
94
95        stream
96            The desired output stream.  If omitted (or false), the standard
97            output stream available at construction will be used.
98
99        """
100        indent = int(indent)
101        width = int(width)
102        assert indent >= 0, "indent must be >= 0"
103        assert depth is None or depth > 0, "depth must be > 0"
104        assert width, "width must be != 0"
105        self._depth = depth
106        self._indent_per_level = indent
107        self._width = width
108        if stream is not None:
109            self._stream = stream
110        else:
111            self._stream = _sys.stdout
112
113    def pprint(self, object):
114        self._format(object, self._stream, 0, 0, {}, 0)
115        self._stream.write("\n")
116
117    def pformat(self, object):
118        sio = _StringIO()
119        self._format(object, sio, 0, 0, {}, 0)
120        return sio.getvalue()
121
122    def isrecursive(self, object):
123        return self.format(object, {}, 0, 0)[2]
124
125    def isreadable(self, object):
126        s, readable, recursive = self.format(object, {}, 0, 0)
127        return readable and not recursive
128
129    def _format(self, object, stream, indent, allowance, context, level):
130        level = level + 1
131        objid = _id(object)
132        if objid in context:
133            stream.write(_recursion(object))
134            self._recursive = True
135            self._readable = False
136            return
137        rep = self._repr(object, context, level - 1)
138        typ = _type(object)
139        sepLines = _len(rep) > (self._width - 1 - indent - allowance)
140        write = stream.write
141
142        if self._depth and level > self._depth:
143            write(rep)
144            return
145
146        r = getattr(typ, "__repr__", None)
147        if issubclass(typ, dict) and r is dict.__repr__:
148            write('{')
149            if self._indent_per_level > 1:
150                write((self._indent_per_level - 1) * ' ')
151            length = _len(object)
152            if length:
153                context[objid] = 1
154                indent = indent + self._indent_per_level
155                items = _sorted(object.items())
156                key, ent = items[0]
157                rep = self._repr(key, context, level)
158                write(rep)
159                write(': ')
160                self._format(ent, stream, indent + _len(rep) + 2,
161                              allowance + 1, context, level)
162                if length > 1:
163                    for key, ent in items[1:]:
164                        rep = self._repr(key, context, level)
165                        if sepLines:
166                            write(',\n%s%s: ' % (' '*indent, rep))
167                        else:
168                            write(', %s: ' % rep)
169                        self._format(ent, stream, indent + _len(rep) + 2,
170                                      allowance + 1, context, level)
171                indent = indent - self._indent_per_level
172                del context[objid]
173            write('}')
174            return
175
176        if ((issubclass(typ, list) and r is list.__repr__) or
177            (issubclass(typ, tuple) and r is tuple.__repr__) or
178            (issubclass(typ, set) and r is set.__repr__) or
179            (issubclass(typ, frozenset) and r is frozenset.__repr__)
180           ):
181            length = _len(object)
182            if issubclass(typ, list):
183                write('[')
184                endchar = ']'
185            elif issubclass(typ, set):
186                if not length:
187                    write('set()')
188                    return
189                write('set([')
190                endchar = '])'
191                object = _sorted(object)
192                indent += 4
193            elif issubclass(typ, frozenset):
194                if not length:
195                    write('frozenset()')
196                    return
197                write('frozenset([')
198                endchar = '])'
199                object = _sorted(object)
200                indent += 10
201            else:
202                write('(')
203                endchar = ')'
204            if self._indent_per_level > 1 and sepLines:
205                write((self._indent_per_level - 1) * ' ')
206            if length:
207                context[objid] = 1
208                indent = indent + self._indent_per_level
209                self._format(object[0], stream, indent, allowance + 1,
210                             context, level)
211                if length > 1:
212                    for ent in object[1:]:
213                        if sepLines:
214                            write(',\n' + ' '*indent)
215                        else:
216                            write(', ')
217                        self._format(ent, stream, indent,
218                                      allowance + 1, context, level)
219                indent = indent - self._indent_per_level
220                del context[objid]
221            if issubclass(typ, tuple) and length == 1:
222                write(',')
223            write(endchar)
224            return
225
226        write(rep)
227
228    def _repr(self, object, context, level):
229        repr, readable, recursive = self.format(object, context.copy(),
230                                                self._depth, level)
231        if not readable:
232            self._readable = False
233        if recursive:
234            self._recursive = True
235        return repr
236
237    def format(self, object, context, maxlevels, level):
238        """Format object for a specific context, returning a string
239        and flags indicating whether the representation is 'readable'
240        and whether the object represents a recursive construct.
241        """
242        return _safe_repr(object, context, maxlevels, level)
243
244
245# Return triple (repr_string, isreadable, isrecursive).
246
247def _safe_repr(object, context, maxlevels, level):
248    typ = _type(object)
249    if typ is str:
250        if 'locale' not in _sys.modules:
251            return repr(object), True, False
252        if "'" in object and '"' not in object:
253            closure = '"'
254            quotes = {'"': '\\"'}
255        else:
256            closure = "'"
257            quotes = {"'": "\\'"}
258        qget = quotes.get
259        sio = _StringIO()
260        write = sio.write
261        for char in object:
262            if char.isalpha():
263                write(char)
264            else:
265                write(qget(char, repr(char)[1:-1]))
266        return ("%s%s%s" % (closure, sio.getvalue(), closure)), True, False
267
268    r = getattr(typ, "__repr__", None)
269    if issubclass(typ, dict) and r is dict.__repr__:
270        if not object:
271            return "{}", True, False
272        objid = _id(object)
273        if maxlevels and level >= maxlevels:
274            return "{...}", False, objid in context
275        if objid in context:
276            return _recursion(object), False, True
277        context[objid] = 1
278        readable = True
279        recursive = False
280        components = []
281        append = components.append
282        level += 1
283        saferepr = _safe_repr
284        for k, v in _sorted(object.items()):
285            krepr, kreadable, krecur = saferepr(k, context, maxlevels, level)
286            vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level)
287            append("%s: %s" % (krepr, vrepr))
288            readable = readable and kreadable and vreadable
289            if krecur or vrecur:
290                recursive = True
291        del context[objid]
292        return "{%s}" % _commajoin(components), readable, recursive
293
294    if (issubclass(typ, list) and r is list.__repr__) or \
295       (issubclass(typ, tuple) and r is tuple.__repr__):
296        if issubclass(typ, list):
297            if not object:
298                return "[]", True, False
299            format = "[%s]"
300        elif _len(object) == 1:
301            format = "(%s,)"
302        else:
303            if not object:
304                return "()", True, False
305            format = "(%s)"
306        objid = _id(object)
307        if maxlevels and level >= maxlevels:
308            return format % "...", False, objid in context
309        if objid in context:
310            return _recursion(object), False, True
311        context[objid] = 1
312        readable = True
313        recursive = False
314        components = []
315        append = components.append
316        level += 1
317        for o in object:
318            orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level)
319            append(orepr)
320            if not oreadable:
321                readable = False
322            if orecur:
323                recursive = True
324        del context[objid]
325        return format % _commajoin(components), readable, recursive
326
327    rep = repr(object)
328    return rep, (rep and not rep.startswith('<')), False
329
330
331def _recursion(object):
332    return ("<Recursion on %s with id=%s>"
333            % (_type(object).__name__, _id(object)))
334
335
336def _perfcheck(object=None):
337    import time
338    if object is None:
339        object = [("string", (1, 2), [3, 4], {5: 6, 7: 8})] * 100000
340    p = PrettyPrinter()
341    t1 = time.time()
342    _safe_repr(object, {}, None, 0)
343    t2 = time.time()
344    p.pformat(object)
345    t3 = time.time()
346    print "_safe_repr:", t2 - t1
347    print "pformat:", t3 - t2
348
349if __name__ == "__main__":
350    _perfcheck()
351