[Python-checkins] cpython: Issue #19105: pprint now more efficiently uses free space at the right.

serhiy.storchaka python-checkins at python.org
Sat Feb 14 10:02:36 CET 2015


https://hg.python.org/cpython/rev/7a6671d491da
changeset:   94606:7a6671d491da
user:        Serhiy Storchaka <storchaka at gmail.com>
date:        Sat Feb 14 10:55:19 2015 +0200
summary:
  Issue #19105: pprint now more efficiently uses free space at the right.

files:
  Lib/pprint.py           |  87 +++++++++++++++++--------
  Lib/test/test_pprint.py |  97 +++++++++++++++++++++++++---
  Misc/NEWS               |   2 +
  3 files changed, 147 insertions(+), 39 deletions(-)


diff --git a/Lib/pprint.py b/Lib/pprint.py
--- a/Lib/pprint.py
+++ b/Lib/pprint.py
@@ -161,7 +161,7 @@
             return
         rep = self._repr(object, context, level - 1)
         typ = type(object)
-        max_width = self._width - 1 - indent - allowance
+        max_width = self._width - indent - allowance
         sepLines = len(rep) > max_width
         write = stream.write
 
@@ -174,24 +174,14 @@
                 length = len(object)
                 if length:
                     context[objid] = 1
-                    indent = indent + self._indent_per_level
                     if issubclass(typ, _OrderedDict):
                         items = list(object.items())
                     else:
                         items = sorted(object.items(), key=_safe_tuple)
-                    key, ent = items[0]
-                    rep = self._repr(key, context, level)
-                    write(rep)
-                    write(': ')
-                    self._format(ent, stream, indent + len(rep) + 2,
-                                  allowance + 1, context, level)
-                    if length > 1:
-                        for key, ent in items[1:]:
-                            rep = self._repr(key, context, level)
-                            write(',\n%s%s: ' % (' '*indent, rep))
-                            self._format(ent, stream, indent + len(rep) + 2,
-                                          allowance + 1, context, level)
-                    indent = indent - self._indent_per_level
+                    self._format_dict_items(items, stream,
+                                            indent + self._indent_per_level,
+                                            allowance + 1,
+                                            context, level)
                     del context[objid]
                 write('}')
                 return
@@ -207,7 +197,10 @@
                     endchar = ']'
                 elif issubclass(typ, tuple):
                     write('(')
-                    endchar = ')'
+                    if length == 1:
+                        endchar = ',)'
+                    else:
+                        endchar = ')'
                 else:
                     if not length:
                         write(rep)
@@ -227,10 +220,9 @@
                     context[objid] = 1
                     self._format_items(object, stream,
                                        indent + self._indent_per_level,
-                                       allowance + 1, context, level)
+                                       allowance + len(endchar),
+                                       context, level)
                     del context[objid]
-                if issubclass(typ, tuple) and length == 1:
-                    write(',')
                 write(endchar)
                 return
 
@@ -239,19 +231,27 @@
                 lines = object.splitlines(True)
                 if level == 1:
                     indent += 1
-                    max_width -= 2
+                    allowance += 1
+                max_width1 = max_width = self._width - indent
                 for i, line in enumerate(lines):
                     rep = repr(line)
-                    if len(rep) <= max_width:
+                    if i == len(lines) - 1:
+                        max_width1 -= allowance
+                    if len(rep) <= max_width1:
                         chunks.append(rep)
                     else:
                         # A list of alternating (non-space, space) strings
-                        parts = re.split(r'(\s+)', line) + ['']
+                        parts = re.findall(r'\S*\s*', line)
+                        assert parts
+                        assert not parts[-1]
+                        parts.pop()  # drop empty last part
+                        max_width2 = max_width
                         current = ''
-                        for i in range(0, len(parts), 2):
-                            part = parts[i] + parts[i+1]
+                        for j, part in enumerate(parts):
                             candidate = current + part
-                            if len(repr(candidate)) > max_width:
+                            if j == len(parts) - 1 and i == len(lines) - 1:
+                                max_width2 -= allowance
+                            if len(repr(candidate)) > max_width2:
                                 if current:
                                     chunks.append(repr(current))
                                 current = part
@@ -273,12 +273,41 @@
                 return
         write(rep)
 
+    def _format_dict_items(self, items, stream, indent, allowance, context,
+                           level):
+        write = stream.write
+        delimnl = ',\n' + ' ' * indent
+        last_index = len(items) - 1
+        for i, (key, ent) in enumerate(items):
+            last = i == last_index
+            rep = self._repr(key, context, level)
+            write(rep)
+            write(': ')
+            self._format(ent, stream, indent + len(rep) + 2,
+                         allowance if last else 1,
+                         context, level)
+            if not last:
+                write(delimnl)
+
     def _format_items(self, items, stream, indent, allowance, context, level):
         write = stream.write
         delimnl = ',\n' + ' ' * indent
         delim = ''
-        width = max_width = self._width - indent - allowance + 2
-        for ent in items:
+        width = max_width = self._width - indent + 1
+        it = iter(items)
+        try:
+            next_ent = next(it)
+        except StopIteration:
+            return
+        last = False
+        while not last:
+            ent = next_ent
+            try:
+                next_ent = next(it)
+            except StopIteration:
+                last = True
+                max_width -= allowance
+                width -= allowance
             if self._compact:
                 rep = self._repr(ent, context, level)
                 w = len(rep) + 2
@@ -294,7 +323,9 @@
                     continue
             write(delim)
             delim = delimnl
-            self._format(ent, stream, indent, allowance, context, level)
+            self._format(ent, stream, indent,
+                         allowance if last else 1,
+                         context, level)
 
     def _repr(self, object, context, level):
         repr, readable, recursive = self.format(object, context.copy(),
diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py
--- a/Lib/test/test_pprint.py
+++ b/Lib/test/test_pprint.py
@@ -192,10 +192,52 @@
         o = [o1, o2]
         expected = """\
 [   [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
+    {'first': 1, 'second': 2, 'third': 3}]"""
+        self.assertEqual(pprint.pformat(o, indent=4, width=42), expected)
+        expected = """\
+[   [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
     {   'first': 1,
         'second': 2,
         'third': 3}]"""
-        self.assertEqual(pprint.pformat(o, indent=4, width=42), expected)
+        self.assertEqual(pprint.pformat(o, indent=4, width=41), expected)
+
+    def test_width(self):
+        expected = """\
+[[[[[[1, 2, 3],
+     '1 2']]]],
+ {1: [1, 2, 3],
+  2: [12, 34]},
+ 'abc def ghi',
+ ('ab cd ef',),
+ set2({1, 23}),
+ [[[[[1, 2, 3],
+     '1 2']]]]]"""
+        o = eval(expected)
+        self.assertEqual(pprint.pformat(o, width=15), expected)
+        self.assertEqual(pprint.pformat(o, width=16), expected)
+        self.assertEqual(pprint.pformat(o, width=25), expected)
+        self.assertEqual(pprint.pformat(o, width=14), """\
+[[[[[[1,
+      2,
+      3],
+     '1 '
+     '2']]]],
+ {1: [1,
+      2,
+      3],
+  2: [12,
+      34]},
+ 'abc def '
+ 'ghi',
+ ('ab cd '
+  'ef',),
+ set2({1,
+       23}),
+ [[[[[1,
+      2,
+      3],
+     '1 '
+     '2']]]]]""")
 
     def test_sorted_dict(self):
         # Starting in Python 2.5, pprint sorts dict displays by key regardless
@@ -535,13 +577,12 @@
     def test_str_wrap(self):
         # pprint tries to wrap strings intelligently
         fox = 'the quick brown fox jumped over a lazy dog'
-        self.assertEqual(pprint.pformat(fox, width=20), """\
-('the quick '
- 'brown fox '
- 'jumped over a '
- 'lazy dog')""")
+        self.assertEqual(pprint.pformat(fox, width=19), """\
+('the quick brown '
+ 'fox jumped over '
+ 'a lazy dog')""")
         self.assertEqual(pprint.pformat({'a': 1, 'b': fox, 'c': 2},
-                                        width=26), """\
+                                        width=25), """\
 {'a': 1,
  'b': 'the quick brown '
       'fox jumped over '
@@ -553,12 +594,34 @@
         # - non-ASCII is allowed
         # - an apostrophe doesn't disrupt the pprint
         special = "Portons dix bons \"whiskys\"\nà l'avocat goujat\t qui fumait au zoo"
-        self.assertEqual(pprint.pformat(special, width=21), """\
-('Portons dix '
- 'bons "whiskys"\\n'
+        self.assertEqual(pprint.pformat(special, width=68), repr(special))
+        self.assertEqual(pprint.pformat(special, width=31), """\
+('Portons dix bons "whiskys"\\n'
+ "à l'avocat goujat\\t qui "
+ 'fumait au zoo')""")
+        self.assertEqual(pprint.pformat(special, width=20), """\
+('Portons dix bons '
+ '"whiskys"\\n'
  "à l'avocat "
  'goujat\\t qui '
  'fumait au zoo')""")
+        self.assertEqual(pprint.pformat([[[[[special]]]]], width=35), """\
+[[[[['Portons dix bons "whiskys"\\n'
+     "à l'avocat goujat\\t qui "
+     'fumait au zoo']]]]]""")
+        self.assertEqual(pprint.pformat([[[[[special]]]]], width=25), """\
+[[[[['Portons dix bons '
+     '"whiskys"\\n'
+     "à l'avocat "
+     'goujat\\t qui '
+     'fumait au zoo']]]]]""")
+        self.assertEqual(pprint.pformat([[[[[special]]]]], width=23), """\
+[[[[['Portons dix '
+     'bons "whiskys"\\n'
+     "à l'avocat "
+     'goujat\\t qui '
+     'fumait au '
+     'zoo']]]]]""")
         # An unwrappable string is formatted as its repr
         unwrappable = "x" * 100
         self.assertEqual(pprint.pformat(unwrappable, width=80), repr(unwrappable))
@@ -581,7 +644,19 @@
   14, 15],
  [], [0], [0, 1], [0, 1, 2], [0, 1, 2, 3],
  [0, 1, 2, 3, 4]]"""
-        self.assertEqual(pprint.pformat(o, width=48, compact=True), expected)
+        self.assertEqual(pprint.pformat(o, width=47, compact=True), expected)
+
+    def test_compact_width(self):
+        levels = 20
+        number = 10
+        o = [0] * number
+        for i in range(levels - 1):
+            o = [o]
+        for w in range(levels * 2 + 1, levels + 3 * number - 1):
+            lines = pprint.pformat(o, width=w, compact=True).splitlines()
+            maxwidth = max(map(len, lines))
+            self.assertLessEqual(maxwidth, w)
+            self.assertGreater(maxwidth, w - 3)
 
 
 class DottedPrettyPrinter(pprint.PrettyPrinter):
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -13,6 +13,8 @@
 Library
 -------
 
+- Issue #19105: pprint now more efficiently uses free space at the right.
+
 - Issue #14910: Add allow_abbrev parameter to argparse.ArgumentParser. Patch by
   Jonathan Paugh, Steven Bethard, paul j3 and Daniel Eriksson.
 

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list