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 collections as _collections 38import re 39import sys as _sys 40import types as _types 41from io import StringIO as _StringIO 42 43__all__ = ["pprint","pformat","isreadable","isrecursive","saferepr", 44 "PrettyPrinter"] 45 46 47def pprint(object, stream=None, indent=1, width=80, depth=None, *, 48 compact=False): 49 """Pretty-print a Python object to a stream [default is sys.stdout].""" 50 printer = PrettyPrinter( 51 stream=stream, indent=indent, width=width, depth=depth, 52 compact=compact) 53 printer.pprint(object) 54 55def pformat(object, indent=1, width=80, depth=None, *, compact=False): 56 """Format a Python object into a pretty-printed representation.""" 57 return PrettyPrinter(indent=indent, width=width, depth=depth, 58 compact=compact).pformat(object) 59 60def saferepr(object): 61 """Version of repr() which can handle recursive data structures.""" 62 return _safe_repr(object, {}, None, 0)[0] 63 64def isreadable(object): 65 """Determine if saferepr(object) is readable by eval().""" 66 return _safe_repr(object, {}, None, 0)[1] 67 68def isrecursive(object): 69 """Determine if object requires a recursive representation.""" 70 return _safe_repr(object, {}, None, 0)[2] 71 72class _safe_key: 73 """Helper function for key functions when sorting unorderable objects. 74 75 The wrapped-object will fallback to a Py2.x style comparison for 76 unorderable types (sorting first comparing the type name and then by 77 the obj ids). Does not work recursively, so dict.items() must have 78 _safe_key applied to both the key and the value. 79 80 """ 81 82 __slots__ = ['obj'] 83 84 def __init__(self, obj): 85 self.obj = obj 86 87 def __lt__(self, other): 88 try: 89 return self.obj < other.obj 90 except TypeError: 91 return ((str(type(self.obj)), id(self.obj)) < \ 92 (str(type(other.obj)), id(other.obj))) 93 94def _safe_tuple(t): 95 "Helper function for comparing 2-tuples" 96 return _safe_key(t[0]), _safe_key(t[1]) 97 98class PrettyPrinter: 99 def __init__(self, indent=1, width=80, depth=None, stream=None, *, 100 compact=False): 101 """Handle pretty printing operations onto a stream using a set of 102 configured parameters. 103 104 indent 105 Number of spaces to indent for each level of nesting. 106 107 width 108 Attempted maximum number of columns in the output. 109 110 depth 111 The maximum depth to print out nested structures. 112 113 stream 114 The desired output stream. If omitted (or false), the standard 115 output stream available at construction will be used. 116 117 compact 118 If true, several items will be combined in one line. 119 120 """ 121 indent = int(indent) 122 width = int(width) 123 if indent < 0: 124 raise ValueError('indent must be >= 0') 125 if depth is not None and depth <= 0: 126 raise ValueError('depth must be > 0') 127 if not width: 128 raise ValueError('width must be != 0') 129 self._depth = depth 130 self._indent_per_level = indent 131 self._width = width 132 if stream is not None: 133 self._stream = stream 134 else: 135 self._stream = _sys.stdout 136 self._compact = bool(compact) 137 138 def pprint(self, object): 139 self._format(object, self._stream, 0, 0, {}, 0) 140 self._stream.write("\n") 141 142 def pformat(self, object): 143 sio = _StringIO() 144 self._format(object, sio, 0, 0, {}, 0) 145 return sio.getvalue() 146 147 def isrecursive(self, object): 148 return self.format(object, {}, 0, 0)[2] 149 150 def isreadable(self, object): 151 s, readable, recursive = self.format(object, {}, 0, 0) 152 return readable and not recursive 153 154 def _format(self, object, stream, indent, allowance, context, level): 155 objid = id(object) 156 if objid in context: 157 stream.write(_recursion(object)) 158 self._recursive = True 159 self._readable = False 160 return 161 rep = self._repr(object, context, level) 162 max_width = self._width - indent - allowance 163 if len(rep) > max_width: 164 p = self._dispatch.get(type(object).__repr__, None) 165 if p is not None: 166 context[objid] = 1 167 p(self, object, stream, indent, allowance, context, level + 1) 168 del context[objid] 169 return 170 elif isinstance(object, dict): 171 context[objid] = 1 172 self._pprint_dict(object, stream, indent, allowance, 173 context, level + 1) 174 del context[objid] 175 return 176 stream.write(rep) 177 178 _dispatch = {} 179 180 def _pprint_dict(self, object, stream, indent, allowance, context, level): 181 write = stream.write 182 write('{') 183 if self._indent_per_level > 1: 184 write((self._indent_per_level - 1) * ' ') 185 length = len(object) 186 if length: 187 items = sorted(object.items(), key=_safe_tuple) 188 self._format_dict_items(items, stream, indent, allowance + 1, 189 context, level) 190 write('}') 191 192 _dispatch[dict.__repr__] = _pprint_dict 193 194 def _pprint_ordered_dict(self, object, stream, indent, allowance, context, level): 195 if not len(object): 196 stream.write(repr(object)) 197 return 198 cls = object.__class__ 199 stream.write(cls.__name__ + '(') 200 self._format(list(object.items()), stream, 201 indent + len(cls.__name__) + 1, allowance + 1, 202 context, level) 203 stream.write(')') 204 205 _dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict 206 207 def _pprint_list(self, object, stream, indent, allowance, context, level): 208 stream.write('[') 209 self._format_items(object, stream, indent, allowance + 1, 210 context, level) 211 stream.write(']') 212 213 _dispatch[list.__repr__] = _pprint_list 214 215 def _pprint_tuple(self, object, stream, indent, allowance, context, level): 216 stream.write('(') 217 endchar = ',)' if len(object) == 1 else ')' 218 self._format_items(object, stream, indent, allowance + len(endchar), 219 context, level) 220 stream.write(endchar) 221 222 _dispatch[tuple.__repr__] = _pprint_tuple 223 224 def _pprint_set(self, object, stream, indent, allowance, context, level): 225 if not len(object): 226 stream.write(repr(object)) 227 return 228 typ = object.__class__ 229 if typ is set: 230 stream.write('{') 231 endchar = '}' 232 else: 233 stream.write(typ.__name__ + '({') 234 endchar = '})' 235 indent += len(typ.__name__) + 1 236 object = sorted(object, key=_safe_key) 237 self._format_items(object, stream, indent, allowance + len(endchar), 238 context, level) 239 stream.write(endchar) 240 241 _dispatch[set.__repr__] = _pprint_set 242 _dispatch[frozenset.__repr__] = _pprint_set 243 244 def _pprint_str(self, object, stream, indent, allowance, context, level): 245 write = stream.write 246 if not len(object): 247 write(repr(object)) 248 return 249 chunks = [] 250 lines = object.splitlines(True) 251 if level == 1: 252 indent += 1 253 allowance += 1 254 max_width1 = max_width = self._width - indent 255 for i, line in enumerate(lines): 256 rep = repr(line) 257 if i == len(lines) - 1: 258 max_width1 -= allowance 259 if len(rep) <= max_width1: 260 chunks.append(rep) 261 else: 262 # A list of alternating (non-space, space) strings 263 parts = re.findall(r'\S*\s*', line) 264 assert parts 265 assert not parts[-1] 266 parts.pop() # drop empty last part 267 max_width2 = max_width 268 current = '' 269 for j, part in enumerate(parts): 270 candidate = current + part 271 if j == len(parts) - 1 and i == len(lines) - 1: 272 max_width2 -= allowance 273 if len(repr(candidate)) > max_width2: 274 if current: 275 chunks.append(repr(current)) 276 current = part 277 else: 278 current = candidate 279 if current: 280 chunks.append(repr(current)) 281 if len(chunks) == 1: 282 write(rep) 283 return 284 if level == 1: 285 write('(') 286 for i, rep in enumerate(chunks): 287 if i > 0: 288 write('\n' + ' '*indent) 289 write(rep) 290 if level == 1: 291 write(')') 292 293 _dispatch[str.__repr__] = _pprint_str 294 295 def _pprint_bytes(self, object, stream, indent, allowance, context, level): 296 write = stream.write 297 if len(object) <= 4: 298 write(repr(object)) 299 return 300 parens = level == 1 301 if parens: 302 indent += 1 303 allowance += 1 304 write('(') 305 delim = '' 306 for rep in _wrap_bytes_repr(object, self._width - indent, allowance): 307 write(delim) 308 write(rep) 309 if not delim: 310 delim = '\n' + ' '*indent 311 if parens: 312 write(')') 313 314 _dispatch[bytes.__repr__] = _pprint_bytes 315 316 def _pprint_bytearray(self, object, stream, indent, allowance, context, level): 317 write = stream.write 318 write('bytearray(') 319 self._pprint_bytes(bytes(object), stream, indent + 10, 320 allowance + 1, context, level + 1) 321 write(')') 322 323 _dispatch[bytearray.__repr__] = _pprint_bytearray 324 325 def _pprint_mappingproxy(self, object, stream, indent, allowance, context, level): 326 stream.write('mappingproxy(') 327 self._format(object.copy(), stream, indent + 13, allowance + 1, 328 context, level) 329 stream.write(')') 330 331 _dispatch[_types.MappingProxyType.__repr__] = _pprint_mappingproxy 332 333 def _format_dict_items(self, items, stream, indent, allowance, context, 334 level): 335 write = stream.write 336 indent += self._indent_per_level 337 delimnl = ',\n' + ' ' * indent 338 last_index = len(items) - 1 339 for i, (key, ent) in enumerate(items): 340 last = i == last_index 341 rep = self._repr(key, context, level) 342 write(rep) 343 write(': ') 344 self._format(ent, stream, indent + len(rep) + 2, 345 allowance if last else 1, 346 context, level) 347 if not last: 348 write(delimnl) 349 350 def _format_items(self, items, stream, indent, allowance, context, level): 351 write = stream.write 352 indent += self._indent_per_level 353 if self._indent_per_level > 1: 354 write((self._indent_per_level - 1) * ' ') 355 delimnl = ',\n' + ' ' * indent 356 delim = '' 357 width = max_width = self._width - indent + 1 358 it = iter(items) 359 try: 360 next_ent = next(it) 361 except StopIteration: 362 return 363 last = False 364 while not last: 365 ent = next_ent 366 try: 367 next_ent = next(it) 368 except StopIteration: 369 last = True 370 max_width -= allowance 371 width -= allowance 372 if self._compact: 373 rep = self._repr(ent, context, level) 374 w = len(rep) + 2 375 if width < w: 376 width = max_width 377 if delim: 378 delim = delimnl 379 if width >= w: 380 width -= w 381 write(delim) 382 delim = ', ' 383 write(rep) 384 continue 385 write(delim) 386 delim = delimnl 387 self._format(ent, stream, indent, 388 allowance if last else 1, 389 context, level) 390 391 def _repr(self, object, context, level): 392 repr, readable, recursive = self.format(object, context.copy(), 393 self._depth, level) 394 if not readable: 395 self._readable = False 396 if recursive: 397 self._recursive = True 398 return repr 399 400 def format(self, object, context, maxlevels, level): 401 """Format object for a specific context, returning a string 402 and flags indicating whether the representation is 'readable' 403 and whether the object represents a recursive construct. 404 """ 405 return _safe_repr(object, context, maxlevels, level) 406 407 def _pprint_default_dict(self, object, stream, indent, allowance, context, level): 408 if not len(object): 409 stream.write(repr(object)) 410 return 411 rdf = self._repr(object.default_factory, context, level) 412 cls = object.__class__ 413 indent += len(cls.__name__) + 1 414 stream.write('%s(%s,\n%s' % (cls.__name__, rdf, ' ' * indent)) 415 self._pprint_dict(object, stream, indent, allowance + 1, context, level) 416 stream.write(')') 417 418 _dispatch[_collections.defaultdict.__repr__] = _pprint_default_dict 419 420 def _pprint_counter(self, object, stream, indent, allowance, context, level): 421 if not len(object): 422 stream.write(repr(object)) 423 return 424 cls = object.__class__ 425 stream.write(cls.__name__ + '({') 426 if self._indent_per_level > 1: 427 stream.write((self._indent_per_level - 1) * ' ') 428 items = object.most_common() 429 self._format_dict_items(items, stream, 430 indent + len(cls.__name__) + 1, allowance + 2, 431 context, level) 432 stream.write('})') 433 434 _dispatch[_collections.Counter.__repr__] = _pprint_counter 435 436 def _pprint_chain_map(self, object, stream, indent, allowance, context, level): 437 if not len(object.maps): 438 stream.write(repr(object)) 439 return 440 cls = object.__class__ 441 stream.write(cls.__name__ + '(') 442 indent += len(cls.__name__) + 1 443 for i, m in enumerate(object.maps): 444 if i == len(object.maps) - 1: 445 self._format(m, stream, indent, allowance + 1, context, level) 446 stream.write(')') 447 else: 448 self._format(m, stream, indent, 1, context, level) 449 stream.write(',\n' + ' ' * indent) 450 451 _dispatch[_collections.ChainMap.__repr__] = _pprint_chain_map 452 453 def _pprint_deque(self, object, stream, indent, allowance, context, level): 454 if not len(object): 455 stream.write(repr(object)) 456 return 457 cls = object.__class__ 458 stream.write(cls.__name__ + '(') 459 indent += len(cls.__name__) + 1 460 stream.write('[') 461 if object.maxlen is None: 462 self._format_items(object, stream, indent, allowance + 2, 463 context, level) 464 stream.write('])') 465 else: 466 self._format_items(object, stream, indent, 2, 467 context, level) 468 rml = self._repr(object.maxlen, context, level) 469 stream.write('],\n%smaxlen=%s)' % (' ' * indent, rml)) 470 471 _dispatch[_collections.deque.__repr__] = _pprint_deque 472 473 def _pprint_user_dict(self, object, stream, indent, allowance, context, level): 474 self._format(object.data, stream, indent, allowance, context, level - 1) 475 476 _dispatch[_collections.UserDict.__repr__] = _pprint_user_dict 477 478 def _pprint_user_list(self, object, stream, indent, allowance, context, level): 479 self._format(object.data, stream, indent, allowance, context, level - 1) 480 481 _dispatch[_collections.UserList.__repr__] = _pprint_user_list 482 483 def _pprint_user_string(self, object, stream, indent, allowance, context, level): 484 self._format(object.data, stream, indent, allowance, context, level - 1) 485 486 _dispatch[_collections.UserString.__repr__] = _pprint_user_string 487 488# Return triple (repr_string, isreadable, isrecursive). 489 490def _safe_repr(object, context, maxlevels, level): 491 typ = type(object) 492 if typ in _builtin_scalars: 493 return repr(object), True, False 494 495 r = getattr(typ, "__repr__", None) 496 if issubclass(typ, dict) and r is dict.__repr__: 497 if not object: 498 return "{}", True, False 499 objid = id(object) 500 if maxlevels and level >= maxlevels: 501 return "{...}", False, objid in context 502 if objid in context: 503 return _recursion(object), False, True 504 context[objid] = 1 505 readable = True 506 recursive = False 507 components = [] 508 append = components.append 509 level += 1 510 saferepr = _safe_repr 511 items = sorted(object.items(), key=_safe_tuple) 512 for k, v in items: 513 krepr, kreadable, krecur = saferepr(k, context, maxlevels, level) 514 vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level) 515 append("%s: %s" % (krepr, vrepr)) 516 readable = readable and kreadable and vreadable 517 if krecur or vrecur: 518 recursive = True 519 del context[objid] 520 return "{%s}" % ", ".join(components), readable, recursive 521 522 if (issubclass(typ, list) and r is list.__repr__) or \ 523 (issubclass(typ, tuple) and r is tuple.__repr__): 524 if issubclass(typ, list): 525 if not object: 526 return "[]", True, False 527 format = "[%s]" 528 elif len(object) == 1: 529 format = "(%s,)" 530 else: 531 if not object: 532 return "()", True, False 533 format = "(%s)" 534 objid = id(object) 535 if maxlevels and level >= maxlevels: 536 return format % "...", False, objid in context 537 if objid in context: 538 return _recursion(object), False, True 539 context[objid] = 1 540 readable = True 541 recursive = False 542 components = [] 543 append = components.append 544 level += 1 545 for o in object: 546 orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level) 547 append(orepr) 548 if not oreadable: 549 readable = False 550 if orecur: 551 recursive = True 552 del context[objid] 553 return format % ", ".join(components), readable, recursive 554 555 rep = repr(object) 556 return rep, (rep and not rep.startswith('<')), False 557 558_builtin_scalars = frozenset({str, bytes, bytearray, int, float, complex, 559 bool, type(None)}) 560 561def _recursion(object): 562 return ("<Recursion on %s with id=%s>" 563 % (type(object).__name__, id(object))) 564 565 566def _perfcheck(object=None): 567 import time 568 if object is None: 569 object = [("string", (1, 2), [3, 4], {5: 6, 7: 8})] * 100000 570 p = PrettyPrinter() 571 t1 = time.time() 572 _safe_repr(object, {}, None, 0) 573 t2 = time.time() 574 p.pformat(object) 575 t3 = time.time() 576 print("_safe_repr:", t2 - t1) 577 print("pformat:", t3 - t2) 578 579def _wrap_bytes_repr(object, width, allowance): 580 current = b'' 581 last = len(object) // 4 * 4 582 for i in range(0, len(object), 4): 583 part = object[i: i+4] 584 candidate = current + part 585 if i == last: 586 width -= allowance 587 if len(repr(candidate)) > width: 588 if current: 589 yield repr(current) 590 current = part 591 else: 592 current = candidate 593 if current: 594 yield repr(current) 595 596if __name__ == "__main__": 597 _perfcheck() 598