pprint.py 24 KB

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