Skip to content

bpo-42914: pprint.pprint function displays integer with underscores #24864

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions Doc/library/pprint.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ The :mod:`pprint` module defines one class:
.. index:: single: ...; placeholder

.. class:: PrettyPrinter(indent=1, width=80, depth=None, stream=None, *, \
compact=False, sort_dicts=True)
compact=False, sort_dicts=True, underscore_numbers=False)

Construct a :class:`PrettyPrinter` instance. This constructor understands
several keyword parameters. An output stream may be set using the *stream*
Expand All @@ -55,14 +55,19 @@ The :mod:`pprint` module defines one class:
will be formatted on a separate line. If *compact* is true, as many items
as will fit within the *width* will be formatted on each output line. If
*sort_dicts* is true (the default), dictionaries will be formatted with their
keys sorted, otherwise they will display in insertion order.
keys sorted, otherwise they will display in insertion order. If
*underscore_numbers* is true, integers will be formatted with
```_``` character for a thousands separator, otherwise underscores are not
displayed (the default).

.. versionchanged:: 3.4
Added the *compact* parameter.

.. versionchanged:: 3.8
Added the *sort_dicts* parameter.

.. versionchanged:: 3.10
Added the *underscore_numbers* parameter.

>>> import pprint
>>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni']
Expand Down Expand Up @@ -91,10 +96,10 @@ The :mod:`pprint` module defines one class:
The :mod:`pprint` module also provides several shortcut functions:

.. function:: pformat(object, indent=1, width=80, depth=None, *, \
compact=False, sort_dicts=True)
compact=False, sort_dicts=True, underscore_numbers=False)

Return the formatted representation of *object* as a string. *indent*,
*width*, *depth*, *compact* and *sort_dicts* will be passed to the
*width*, *depth*, *compact*, *sort_dicts* and *underscore_numbers* will be passed to the
:class:`PrettyPrinter` constructor as formatting parameters.

.. versionchanged:: 3.4
Expand All @@ -103,6 +108,9 @@ The :mod:`pprint` module also provides several shortcut functions:
.. versionchanged:: 3.8
Added the *sort_dicts* parameter.

.. versionchanged:: 3.10
Added the *underscore_numbers* parameter.


.. function:: pp(object, *args, sort_dicts=False, **kwargs)

Expand All @@ -116,13 +124,13 @@ The :mod:`pprint` module also provides several shortcut functions:


.. function:: pprint(object, stream=None, indent=1, width=80, depth=None, *, \
compact=False, sort_dicts=True)
compact=False, sort_dicts=True, underscore_numbers=False)

Prints the formatted representation of *object* on *stream*, followed by a
newline. If *stream* is ``None``, ``sys.stdout`` is used. This may be used
in the interactive interpreter instead of the :func:`print` function for
inspecting values (you can even reassign ``print = pprint.pprint`` for use
within a scope). *indent*, *width*, *depth*, *compact* and *sort_dicts* will
within a scope). *indent*, *width*, *depth*, *compact*, *sort_dicts* and *underscore_numbers* will
be passed to the :class:`PrettyPrinter` constructor as formatting parameters.

.. versionchanged:: 3.4
Expand All @@ -131,6 +139,9 @@ The :mod:`pprint` module also provides several shortcut functions:
.. versionchanged:: 3.8
Added the *sort_dicts* parameter.

.. versionchanged:: 3.10
Added the *underscore_numbers* parameter.

>>> import pprint
>>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni']
>>> stuff.insert(0, stuff)
Expand Down
21 changes: 15 additions & 6 deletions Lib/pprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,19 @@


def pprint(object, stream=None, indent=1, width=80, depth=None, *,
compact=False, sort_dicts=True):
compact=False, sort_dicts=True, underscore_numbers=False):
"""Pretty-print a Python object to a stream [default is sys.stdout]."""
printer = PrettyPrinter(
stream=stream, indent=indent, width=width, depth=depth,
compact=compact, sort_dicts=sort_dicts)
compact=compact, sort_dicts=sort_dicts, underscore_numbers=False)
printer.pprint(object)

def pformat(object, indent=1, width=80, depth=None, *,
compact=False, sort_dicts=True):
compact=False, sort_dicts=True, underscore_numbers=False):
"""Format a Python object into a pretty-printed representation."""
return PrettyPrinter(indent=indent, width=width, depth=depth,
compact=compact, sort_dicts=sort_dicts).pformat(object)
compact=compact, sort_dicts=sort_dicts,
underscore_numbers=underscore_numbers).pformat(object)

def pp(object, *args, sort_dicts=False, **kwargs):
"""Pretty-print a Python object"""
Expand Down Expand Up @@ -102,7 +103,7 @@ def _safe_tuple(t):

class PrettyPrinter:
def __init__(self, indent=1, width=80, depth=None, stream=None, *,
compact=False, sort_dicts=True):
compact=False, sort_dicts=True, underscore_numbers=False):
"""Handle pretty printing operations onto a stream using a set of
configured parameters.

Expand Down Expand Up @@ -143,6 +144,7 @@ def __init__(self, indent=1, width=80, depth=None, stream=None, *,
self._stream = _sys.stdout
self._compact = bool(compact)
self._sort_dicts = sort_dicts
self._underscore_numbers = underscore_numbers

def pprint(self, object):
self._format(object, self._stream, 0, 0, {}, 0)
Expand Down Expand Up @@ -525,6 +527,13 @@ def _safe_repr(self, object, context, maxlevels, level):
return repr(object), True, False

r = getattr(typ, "__repr__", None)

if issubclass(typ, int) and r is int.__repr__:
if self._underscore_numbers:
return f"{object:_d}", True, False
else:
return repr(object), True, False

if issubclass(typ, dict) and r is dict.__repr__:
if not object:
return "{}", True, False
Expand Down Expand Up @@ -592,7 +601,7 @@ def _safe_repr(self, object, context, maxlevels, level):
rep = repr(object)
return rep, (rep and not rep.startswith('<')), False

_builtin_scalars = frozenset({str, bytes, bytearray, int, float, complex,
_builtin_scalars = frozenset({str, bytes, bytearray, float, complex,
bool, type(None)})

def _recursion(object):
Expand Down
13 changes: 13 additions & 0 deletions Lib/test/test_pprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ def test_same_as_repr(self):
self.assertEqual(pprint.pformat(simple), native)
self.assertEqual(pprint.pformat(simple, width=1, indent=0)
.replace('\n', ' '), native)
self.assertEqual(pprint.pformat(simple, underscore_numbers=True), native)
self.assertEqual(pprint.saferepr(simple), native)

def test_container_repr_override_called(self):
Expand Down Expand Up @@ -323,6 +324,18 @@ def test_width(self):
'1 '
'2']]]]]""")

def test_integer(self):
self.assertEqual(pprint.pformat(1234567), '1234567')
self.assertEqual(pprint.pformat(1234567, underscore_numbers=True), '1_234_567')

class Temperature(int):
def __new__(cls, celsius_degrees):
return super().__new__(Temperature, celsius_degrees)
def __repr__(self):
kelvin_degrees = self + 273.15
return f"{kelvin_degrees}°K"
self.assertEqual(pprint.pformat(Temperature(1000)), '1273.15°K')

def test_sorted_dict(self):
# Starting in Python 2.5, pprint sorts dict displays by key regardless
# of how small the dictionary may be.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:func:`pprint.pprint` gains a new boolean ``underscore_numbers`` optional
argument to emit integers with thousands separated by an underscore character
for improved readability (for example ``1_000_000`` instead of ``1000000``).