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