]> granicus.if.org Git - python/commitdiff
Issue #19105: pprint now more efficiently uses free space at the right.
authorSerhiy Storchaka <storchaka@gmail.com>
Sat, 14 Feb 2015 08:55:19 +0000 (10:55 +0200)
committerSerhiy Storchaka <storchaka@gmail.com>
Sat, 14 Feb 2015 08:55:19 +0000 (10:55 +0200)
Lib/pprint.py
Lib/test/test_pprint.py
Misc/NEWS

index 2cbffed5d8db508a24b5d94b52f6759c80cc7d65..0091e6925ba5c673c4f7d04e104f300bf9a1b70c 100644 (file)
@@ -161,7 +161,7 @@ class PrettyPrinter:
             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 @@ class PrettyPrinter:
                 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 @@ class PrettyPrinter:
                     endchar = ']'
                 elif issubclass(typ, tuple):
                     write('(')
-                    endchar = ')'
+                    if length == 1:
+                        endchar = ',)'
+                    else:
+                        endchar = ')'
                 else:
                     if not length:
                         write(rep)
@@ -227,10 +220,9 @@ class PrettyPrinter:
                     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 @@ class PrettyPrinter:
                 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 @@ class PrettyPrinter:
                 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 @@ class PrettyPrinter:
                     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(),
index ad6a7a144a06653dba6e22847dfbc672537d5fa9..c0568808a075a46e0062636f77248c1eb67319e5 100644 (file)
@@ -191,11 +191,53 @@ class QueryTestCase(unittest.TestCase):
         o2 = dict(first=1, second=2, third=3)
         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 @@ frozenset2({0,
     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 @@ frozenset2({0,
         # - 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 @@ frozenset2({0,
   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):
index 27b58e30451988af7bfcb160ad2dcef79494b537..9259d2cbc7e30c42e5830ac3fe80206f7626e24e 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -13,6 +13,8 @@ Core and Builtins
 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.