Commit 96831c7f authored by Rémi Lapeyre's avatar Rémi Lapeyre Committed by Raymond Hettinger

bpo-30670: Add pp function to the pprint module (GH-11769)

parent c5c6cdad
...@@ -33,7 +33,7 @@ The :mod:`pprint` module defines one class: ...@@ -33,7 +33,7 @@ The :mod:`pprint` module defines one class:
.. index:: single: ...; placeholder .. index:: single: ...; placeholder
.. class:: PrettyPrinter(indent=1, width=80, depth=None, stream=None, *, \ .. 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 Construct a :class:`PrettyPrinter` instance. This constructor understands
several keyword parameters. An output stream may be set using the *stream* several keyword parameters. An output stream may be set using the *stream*
...@@ -50,11 +50,17 @@ The :mod:`pprint` module defines one class: ...@@ -50,11 +50,17 @@ The :mod:`pprint` module defines one class:
structure cannot be formatted within the constrained width, a best effort will 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 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 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 .. versionchanged:: 3.4
Added the *compact* parameter. Added the *compact* parameter.
.. versionchanged:: 3.8
Added the *sort_dicts* parameter.
>>> import pprint >>> import pprint
>>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni'] >>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni']
>>> stuff.insert(0, stuff[:]) >>> stuff.insert(0, stuff[:])
...@@ -81,29 +87,47 @@ The :mod:`pprint` module defines one class: ...@@ -81,29 +87,47 @@ The :mod:`pprint` module defines one class:
The :mod:`pprint` module also provides several shortcut functions: 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*, Return the formatted representation of *object* as a string. *indent*,
*width*, *depth* and *compact* will be passed to the :class:`PrettyPrinter` *width*, *depth*, *compact* and *sort_dicts* will be passed to the
constructor as formatting parameters. :class:`PrettyPrinter` constructor as formatting parameters.
.. versionchanged:: 3.4 .. versionchanged:: 3.4
Added the *compact* parameter. Added the *compact* parameter.
.. versionchanged:: 3.8
Added the *sort_dicts* parameter.
.. function:: pp(object, *args, sort_dicts=False, **kwargs)
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, *, \ .. 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 Prints the formatted representation of *object* on *stream*, followed by a
newline. If *stream* is ``None``, ``sys.stdout`` is used. This may be used newline. If *stream* is ``None``, ``sys.stdout`` is used. This may be used
in the interactive interpreter instead of the :func:`print` function for in the interactive interpreter instead of the :func:`print` function for
inspecting values (you can even reassign ``print = pprint.pprint`` for use inspecting values (you can even reassign ``print = pprint.pprint`` for use
within a scope). *indent*, *width*, *depth* and *compact* will be passed within a scope). *indent*, *width*, *depth*, *compact* and *sort_dicts* will
to the :class:`PrettyPrinter` constructor as formatting parameters. be passed to the :class:`PrettyPrinter` constructor as formatting parameters.
.. versionchanged:: 3.4 .. versionchanged:: 3.4
Added the *compact* parameter. Added the *compact* parameter.
.. versionchanged:: 3.8
Added the *sort_dicts* parameter.
>>> import pprint >>> import pprint
>>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni'] >>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni']
>>> stuff.insert(0, stuff) >>> stuff.insert(0, stuff)
......
...@@ -180,15 +180,15 @@ library/pickle,,:memory,"conn = sqlite3.connect("":memory:"")" ...@@ -180,15 +180,15 @@ library/pickle,,:memory,"conn = sqlite3.connect("":memory:"")"
library/posix,,`,"CFLAGS=""`getconf LFS_CFLAGS`"" OPT=""-g -O2 $CFLAGS""" library/posix,,`,"CFLAGS=""`getconf LFS_CFLAGS`"" OPT=""-g -O2 $CFLAGS"""
library/pprint,,::,"'Programming Language :: Python :: 2.6'," library/pprint,,::,"'Programming Language :: Python :: 2.6',"
library/pprint,,::,"'Programming Language :: Python :: 2.7'," library/pprint,,::,"'Programming Language :: Python :: 2.7',"
library/pprint,225,::,"'classifiers': ['Development Status :: 3 - Alpha'," library/pprint,,::,"'classifiers': ['Development Status :: 3 - Alpha',"
library/pprint,225,::,"'Intended Audience :: Developers'," library/pprint,,::,"'Intended Audience :: Developers',"
library/pprint,225,::,"'License :: OSI Approved :: MIT License'," library/pprint,,::,"'License :: OSI Approved :: MIT License',"
library/pprint,225,::,"'Programming Language :: Python :: 2'," library/pprint,,::,"'Programming Language :: Python :: 2',"
library/pprint,225,::,"'Programming Language :: Python :: 3'," library/pprint,,::,"'Programming Language :: Python :: 3',"
library/pprint,225,::,"'Programming Language :: Python :: 3.2'," library/pprint,,::,"'Programming Language :: Python :: 3.2',"
library/pprint,225,::,"'Programming Language :: Python :: 3.3'," library/pprint,,::,"'Programming Language :: Python :: 3.3',"
library/pprint,225,::,"'Programming Language :: Python :: 3.4'," library/pprint,,::,"'Programming Language :: Python :: 3.4',"
library/pprint,225,::,"'Topic :: Software Development :: Build Tools']," library/pprint,,::,"'Topic :: Software Development :: Build Tools'],"
library/profile,,:lineno,filename:lineno(function) library/profile,,:lineno,filename:lineno(function)
library/pyexpat,,:elem1,<py:elem1 /> library/pyexpat,,:elem1,<py:elem1 />
library/pyexpat,,:py,"xmlns:py = ""http://www.python.org/ns/"">" library/pyexpat,,:py,"xmlns:py = ""http://www.python.org/ns/"">"
......
...@@ -41,33 +41,38 @@ import types as _types ...@@ -41,33 +41,38 @@ import types as _types
from io import StringIO as _StringIO from io import StringIO as _StringIO
__all__ = ["pprint","pformat","isreadable","isrecursive","saferepr", __all__ = ["pprint","pformat","isreadable","isrecursive","saferepr",
"PrettyPrinter"] "PrettyPrinter", "pp"]
def pprint(object, stream=None, indent=1, width=80, depth=None, *, 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].""" """Pretty-print a Python object to a stream [default is sys.stdout]."""
printer = PrettyPrinter( printer = PrettyPrinter(
stream=stream, indent=indent, width=width, depth=depth, stream=stream, indent=indent, width=width, depth=depth,
compact=compact) compact=compact, sort_dicts=sort_dicts)
printer.pprint(object) 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.""" """Format a Python object into a pretty-printed representation."""
return PrettyPrinter(indent=indent, width=width, depth=depth, 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): def saferepr(object):
"""Version of repr() which can handle recursive data structures.""" """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): def isreadable(object):
"""Determine if saferepr(object) is readable by eval().""" """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): def isrecursive(object):
"""Determine if object requires a recursive representation.""" """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: class _safe_key:
"""Helper function for key functions when sorting unorderable objects. """Helper function for key functions when sorting unorderable objects.
...@@ -97,7 +102,7 @@ def _safe_tuple(t): ...@@ -97,7 +102,7 @@ def _safe_tuple(t):
class PrettyPrinter: class PrettyPrinter:
def __init__(self, indent=1, width=80, depth=None, stream=None, *, 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 """Handle pretty printing operations onto a stream using a set of
configured parameters. configured parameters.
...@@ -117,6 +122,9 @@ class PrettyPrinter: ...@@ -117,6 +122,9 @@ class PrettyPrinter:
compact compact
If true, several items will be combined in one line. If true, several items will be combined in one line.
sort_dicts
If true, dict keys are sorted.
""" """
indent = int(indent) indent = int(indent)
width = int(width) width = int(width)
...@@ -134,6 +142,7 @@ class PrettyPrinter: ...@@ -134,6 +142,7 @@ class PrettyPrinter:
else: else:
self._stream = _sys.stdout self._stream = _sys.stdout
self._compact = bool(compact) self._compact = bool(compact)
self._sort_dicts = sort_dicts
def pprint(self, object): def pprint(self, object):
self._format(object, self._stream, 0, 0, {}, 0) self._format(object, self._stream, 0, 0, {}, 0)
...@@ -184,7 +193,10 @@ class PrettyPrinter: ...@@ -184,7 +193,10 @@ class PrettyPrinter:
write((self._indent_per_level - 1) * ' ') write((self._indent_per_level - 1) * ' ')
length = len(object) length = len(object)
if length: if length:
if self._sort_dicts:
items = sorted(object.items(), key=_safe_tuple) items = sorted(object.items(), key=_safe_tuple)
else:
items = object.items()
self._format_dict_items(items, stream, indent, allowance + 1, self._format_dict_items(items, stream, indent, allowance + 1,
context, level) context, level)
write('}') write('}')
...@@ -402,7 +414,7 @@ class PrettyPrinter: ...@@ -402,7 +414,7 @@ class PrettyPrinter:
and flags indicating whether the representation is 'readable' and flags indicating whether the representation is 'readable'
and whether the object represents a recursive construct. 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): def _pprint_default_dict(self, object, stream, indent, allowance, context, level):
if not len(object): if not len(object):
...@@ -487,7 +499,7 @@ class PrettyPrinter: ...@@ -487,7 +499,7 @@ class PrettyPrinter:
# Return triple (repr_string, isreadable, isrecursive). # 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) typ = type(object)
if typ in _builtin_scalars: if typ in _builtin_scalars:
return repr(object), True, False return repr(object), True, False
...@@ -507,11 +519,13 @@ def _safe_repr(object, context, maxlevels, level): ...@@ -507,11 +519,13 @@ def _safe_repr(object, context, maxlevels, level):
components = [] components = []
append = components.append append = components.append
level += 1 level += 1
saferepr = _safe_repr if sort_dicts:
items = sorted(object.items(), key=_safe_tuple) items = sorted(object.items(), key=_safe_tuple)
else:
items = object.items()
for k, v in items: for k, v in items:
krepr, kreadable, krecur = saferepr(k, context, maxlevels, level) krepr, kreadable, krecur = _safe_repr(k, context, maxlevels, level, sort_dicts)
vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level) vrepr, vreadable, vrecur = _safe_repr(v, context, maxlevels, level, sort_dicts)
append("%s: %s" % (krepr, vrepr)) append("%s: %s" % (krepr, vrepr))
readable = readable and kreadable and vreadable readable = readable and kreadable and vreadable
if krecur or vrecur: if krecur or vrecur:
...@@ -543,7 +557,7 @@ def _safe_repr(object, context, maxlevels, level): ...@@ -543,7 +557,7 @@ def _safe_repr(object, context, maxlevels, level):
append = components.append append = components.append
level += 1 level += 1
for o in object: 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) append(orepr)
if not oreadable: if not oreadable:
readable = False readable = False
...@@ -569,7 +583,7 @@ def _perfcheck(object=None): ...@@ -569,7 +583,7 @@ def _perfcheck(object=None):
object = [("string", (1, 2), [3, 4], {5: 6, 7: 8})] * 100000 object = [("string", (1, 2), [3, 4], {5: 6, 7: 8})] * 100000
p = PrettyPrinter() p = PrettyPrinter()
t1 = time.perf_counter() t1 = time.perf_counter()
_safe_repr(object, {}, None, 0) _safe_repr(object, {}, None, 0, True)
t2 = time.perf_counter() t2 = time.perf_counter()
p.pformat(object) p.pformat(object)
t3 = time.perf_counter() t3 = time.perf_counter()
......
...@@ -81,6 +81,7 @@ class QueryTestCase(unittest.TestCase): ...@@ -81,6 +81,7 @@ class QueryTestCase(unittest.TestCase):
pp = pprint.PrettyPrinter(indent=4, width=40, depth=5, pp = pprint.PrettyPrinter(indent=4, width=40, depth=5,
stream=io.StringIO(), compact=True) stream=io.StringIO(), compact=True)
pp = pprint.PrettyPrinter(4, 40, 5, io.StringIO()) pp = pprint.PrettyPrinter(4, 40, 5, io.StringIO())
pp = pprint.PrettyPrinter(sort_dicts=False)
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
pp = pprint.PrettyPrinter(4, 40, 5, io.StringIO(), True) pp = pprint.PrettyPrinter(4, 40, 5, io.StringIO(), True)
self.assertRaises(ValueError, pprint.PrettyPrinter, indent=-1) self.assertRaises(ValueError, pprint.PrettyPrinter, indent=-1)
...@@ -293,6 +294,12 @@ class QueryTestCase(unittest.TestCase): ...@@ -293,6 +294,12 @@ class QueryTestCase(unittest.TestCase):
self.assertEqual(pprint.pformat({"xy\tab\n": (3,), 5: [[]], (): {}}), self.assertEqual(pprint.pformat({"xy\tab\n": (3,), 5: [[]], (): {}}),
r"{5: [[]], 'xy\tab\n': (3,), (): {}}") 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): def test_ordered_dict(self):
d = collections.OrderedDict() d = collections.OrderedDict()
self.assertEqual(pprint.pformat(d, width=1), 'OrderedDict()') self.assertEqual(pprint.pformat(d, width=1), 'OrderedDict()')
......
`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.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment