[Python-checkins] bpo-30670: Add pp function to the pprint module (GH-11769)

Raymond Hettinger webhook-mailer at python.org
Fri Mar 22 13:22:24 EDT 2019


https://github.com/python/cpython/commit/96831c7fcf888af187bbae8254608cccb4d6a03c
commit: 96831c7fcf888af187bbae8254608cccb4d6a03c
branch: master
author: Rémi Lapeyre <remi.lapeyre at henki.fr>
committer: Raymond Hettinger <rhettinger at users.noreply.github.com>
date: 2019-03-22T10:22:20-07:00
summary:

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

files:
A Misc/NEWS.d/next/Library/2019-02-06-12-07-46.bpo-30670.yffB3F.rst
M Doc/library/pprint.rst
M Doc/tools/susp-ignored.csv
M Lib/pprint.py
M Lib/test/test_pprint.py

diff --git a/Doc/library/pprint.rst b/Doc/library/pprint.rst
index deadf1820851..988f85bed103 100644
--- a/Doc/library/pprint.rst
+++ b/Doc/library/pprint.rst
@@ -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)
+
+   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)
diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv
index 68a278c1f4a7..3672955bf55b 100644
--- a/Doc/tools/susp-ignored.csv
+++ b/Doc/tools/susp-ignored.csv
@@ -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/"">"
diff --git a/Lib/pprint.py b/Lib/pprint.py
index f2a117864e5e..4bfcc31b25ea 100644
--- a/Lib/pprint.py
+++ b/Lib/pprint.py
@@ -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()
diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py
index 7ebc298337ad..269ac0624eeb 100644
--- a/Lib/test/test_pprint.py
+++ b/Lib/test/test_pprint.py
@@ -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()')
diff --git a/Misc/NEWS.d/next/Library/2019-02-06-12-07-46.bpo-30670.yffB3F.rst b/Misc/NEWS.d/next/Library/2019-02-06-12-07-46.bpo-30670.yffB3F.rst
new file mode 100644
index 000000000000..63cdbb363f76
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-02-06-12-07-46.bpo-30670.yffB3F.rst
@@ -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.



More information about the Python-checkins mailing list