pprint.py 19 KB

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