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