Skip to content
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

bpo-30670: Add pp function to the pprint module #11769

Merged
merged 10 commits into from
Mar 22, 2019
Merged
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
40 changes: 32 additions & 8 deletions Doc/library/pprint.rst
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ The :mod:`pprint` module defines one class:
.. index:: single: ...; placeholder

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

Construct a :class:`PrettyPrinter` instance. This constructor understands
several keyword parameters. An output stream may be set using the *stream*
@@ -50,11 +50,17 @@ The :mod:`pprint` module defines one class:
structure cannot be formatted within the constrained width, a best effort will
be made. If *compact* is false (the default) each item of a long sequence
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.
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.

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

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


>>> import pprint
>>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni']
>>> stuff.insert(0, stuff[:])
@@ -81,29 +87,47 @@ 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)
.. function:: pformat(object, indent=1, width=80, depth=None, *, \
compact=False, sort_dicts=True)

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

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

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


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worth explicitely saying that this is an alias of pprint() with sort_dict=False ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is already a mention of *args and **kwargs being passed to pprint and sort_dicts=False is in the signature of pp so I don't think it is necessary.

I would like to also like to avoid giving to much details about pp implementation because we may change some things if another data structure of Python changes like dicts did.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(if we were to do change what this function does, we would also change the doc, but I'm good with that.)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worth explicitely saying that this is an alias of pprint() with sort_dict=False ?

At first glance I was puzzled why both pp() and pprint() existed, and it took a moment to spot the different default sort_dict value - and I ended up having to confirm via source code inspection that there were no other differences between the two.

As such, the above mentioned docs would have been useful for me personally :-)


Prints the formatted representation of *object* followed by a newline.
If *sort_dicts* is false (the default), dictionaries will be displayed with
their keys in insertion order, otherwise the dict keys will be sorted.
*args* an *kwargs* will be passed to :func:`pprint` as formatting
parameters.

.. versionadded:: 3.8


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

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* and *compact* will be passed
to the :class:`PrettyPrinter` constructor as formatting parameters.
within a scope). *indent*, *width*, *depth*, *compact* and *sort_dicts* will
be passed to the :class:`PrettyPrinter` constructor as formatting parameters.

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

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

>>> import pprint
>>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni']
>>> stuff.insert(0, stuff)
18 changes: 9 additions & 9 deletions Doc/tools/susp-ignored.csv
Original file line number Diff line number Diff line change
@@ -180,15 +180,15 @@ library/pickle,,:memory,"conn = sqlite3.connect("":memory:"")"
library/posix,,`,"CFLAGS=""`getconf LFS_CFLAGS`"" OPT=""-g -O2 $CFLAGS"""
library/pprint,,::,"'Programming Language :: Python :: 2.6',"
library/pprint,,::,"'Programming Language :: Python :: 2.7',"
library/pprint,225,::,"'classifiers': ['Development Status :: 3 - Alpha',"
library/pprint,225,::,"'Intended Audience :: Developers',"
library/pprint,225,::,"'License :: OSI Approved :: MIT License',"
library/pprint,225,::,"'Programming Language :: Python :: 2',"
library/pprint,225,::,"'Programming Language :: Python :: 3',"
library/pprint,225,::,"'Programming Language :: Python :: 3.2',"
library/pprint,225,::,"'Programming Language :: Python :: 3.3',"
library/pprint,225,::,"'Programming Language :: Python :: 3.4',"
library/pprint,225,::,"'Topic :: Software Development :: Build Tools'],"
library/pprint,,::,"'classifiers': ['Development Status :: 3 - Alpha',"
library/pprint,,::,"'Intended Audience :: Developers',"
library/pprint,,::,"'License :: OSI Approved :: MIT License',"
library/pprint,,::,"'Programming Language :: Python :: 2',"
library/pprint,,::,"'Programming Language :: Python :: 3',"
library/pprint,,::,"'Programming Language :: Python :: 3.2',"
library/pprint,,::,"'Programming Language :: Python :: 3.3',"
library/pprint,,::,"'Programming Language :: Python :: 3.4',"
library/pprint,,::,"'Topic :: Software Development :: Build Tools'],"
library/profile,,:lineno,filename:lineno(function)
library/pyexpat,,:elem1,<py:elem1 />
library/pyexpat,,:py,"xmlns:py = ""http://www.python.org/ns/"">"
50 changes: 32 additions & 18 deletions Lib/pprint.py
Original file line number Diff line number Diff line change
@@ -41,33 +41,38 @@
from io import StringIO as _StringIO

__all__ = ["pprint","pformat","isreadable","isrecursive","saferepr",
"PrettyPrinter"]
"PrettyPrinter", "pp"]


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

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

def pp(object, *args, sort_dicts=False, **kwargs):
"""Pretty-print a Python object"""
pprint(object, *args, sort_dicts=sort_dicts, **kwargs)

def saferepr(object):
"""Version of repr() which can handle recursive data structures."""
return _safe_repr(object, {}, None, 0)[0]
return _safe_repr(object, {}, None, 0, True)[0]

def isreadable(object):
"""Determine if saferepr(object) is readable by eval()."""
return _safe_repr(object, {}, None, 0)[1]
return _safe_repr(object, {}, None, 0, True)[1]

def isrecursive(object):
"""Determine if object requires a recursive representation."""
return _safe_repr(object, {}, None, 0)[2]
return _safe_repr(object, {}, None, 0, True)[2]

class _safe_key:
"""Helper function for key functions when sorting unorderable objects.
@@ -97,7 +102,7 @@ def _safe_tuple(t):

class PrettyPrinter:
def __init__(self, indent=1, width=80, depth=None, stream=None, *,
compact=False):
compact=False, sort_dicts=True):
"""Handle pretty printing operations onto a stream using a set of
configured parameters.
@@ -117,6 +122,9 @@ def __init__(self, indent=1, width=80, depth=None, stream=None, *,
compact
If true, several items will be combined in one line.
sort_dicts
If true, dict keys are sorted.
"""
indent = int(indent)
width = int(width)
@@ -134,6 +142,7 @@ def __init__(self, indent=1, width=80, depth=None, stream=None, *,
else:
self._stream = _sys.stdout
self._compact = bool(compact)
self._sort_dicts = sort_dicts

def pprint(self, object):
self._format(object, self._stream, 0, 0, {}, 0)
@@ -184,7 +193,10 @@ def _pprint_dict(self, object, stream, indent, allowance, context, level):
write((self._indent_per_level - 1) * ' ')
length = len(object)
if length:
items = sorted(object.items(), key=_safe_tuple)
if self._sort_dicts:
items = sorted(object.items(), key=_safe_tuple)
else:
items = object.items()
self._format_dict_items(items, stream, indent, allowance + 1,
context, level)
write('}')
@@ -402,7 +414,7 @@ def format(self, object, context, maxlevels, level):
and flags indicating whether the representation is 'readable'
and whether the object represents a recursive construct.
"""
return _safe_repr(object, context, maxlevels, level)
return _safe_repr(object, context, maxlevels, level, self._sort_dicts)

def _pprint_default_dict(self, object, stream, indent, allowance, context, level):
if not len(object):
@@ -487,7 +499,7 @@ def _pprint_user_string(self, object, stream, indent, allowance, context, level)

# Return triple (repr_string, isreadable, isrecursive).

def _safe_repr(object, context, maxlevels, level):
def _safe_repr(object, context, maxlevels, level, sort_dicts):
typ = type(object)
if typ in _builtin_scalars:
return repr(object), True, False
@@ -507,11 +519,13 @@ def _safe_repr(object, context, maxlevels, level):
components = []
append = components.append
level += 1
saferepr = _safe_repr
items = sorted(object.items(), key=_safe_tuple)
if sort_dicts:
items = sorted(object.items(), key=_safe_tuple)
else:
items = object.items()
for k, v in items:
krepr, kreadable, krecur = saferepr(k, context, maxlevels, level)
vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level)
krepr, kreadable, krecur = _safe_repr(k, context, maxlevels, level, sort_dicts)
vrepr, vreadable, vrecur = _safe_repr(v, context, maxlevels, level, sort_dicts)
append("%s: %s" % (krepr, vrepr))
readable = readable and kreadable and vreadable
if krecur or vrecur:
@@ -543,7 +557,7 @@ def _safe_repr(object, context, maxlevels, level):
append = components.append
level += 1
for o in object:
orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level)
orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level, sort_dicts)
append(orepr)
if not oreadable:
readable = False
@@ -569,7 +583,7 @@ def _perfcheck(object=None):
object = [("string", (1, 2), [3, 4], {5: 6, 7: 8})] * 100000
p = PrettyPrinter()
t1 = time.perf_counter()
_safe_repr(object, {}, None, 0)
_safe_repr(object, {}, None, 0, True)
t2 = time.perf_counter()
p.pformat(object)
t3 = time.perf_counter()
7 changes: 7 additions & 0 deletions Lib/test/test_pprint.py
Original file line number Diff line number Diff line change
@@ -81,6 +81,7 @@ def test_init(self):
pp = pprint.PrettyPrinter(indent=4, width=40, depth=5,
stream=io.StringIO(), compact=True)
pp = pprint.PrettyPrinter(4, 40, 5, io.StringIO())
pp = pprint.PrettyPrinter(sort_dicts=False)
with self.assertRaises(TypeError):
pp = pprint.PrettyPrinter(4, 40, 5, io.StringIO(), True)
self.assertRaises(ValueError, pprint.PrettyPrinter, indent=-1)
@@ -293,6 +294,12 @@ def test_sorted_dict(self):
self.assertEqual(pprint.pformat({"xy\tab\n": (3,), 5: [[]], (): {}}),
r"{5: [[]], 'xy\tab\n': (3,), (): {}}")

def test_sort_dict(self):
d = dict.fromkeys('cba')
self.assertEqual(pprint.pformat(d, sort_dicts=False), "{'c': None, 'b': None, 'a': None}")
self.assertEqual(pprint.pformat([d, d], sort_dicts=False),
"[{'c': None, 'b': None, 'a': None}, {'c': None, 'b': None, 'a': None}]")

def test_ordered_dict(self):
d = collections.OrderedDict()
self.assertEqual(pprint.pformat(d, width=1), 'OrderedDict()')
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
`pprint.pp` has been added to pretty-print objects with dictionary
keys being sorted with their insertion order by default. Parameter
*sort_dicts* has been added to `pprint.pprint`, `pprint.pformat` and
`pprint.PrettyPrinter`. Contributed by Rémi Lapeyre.