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