logo

oasis-root

Compiled tree of Oasis Linux based on own branch at <https://hacktivis.me/git/oasis/> git clone https://anongit.hacktivis.me/git/oasis-root.git

pprint.py (24423B)


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