summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2015-02-14 08:55:19 (GMT)
committerSerhiy Storchaka <storchaka@gmail.com>2015-02-14 08:55:19 (GMT)
commita750ce3325b0daf6942834efd3644d1cf837abad (patch)
treed9d01e8cc86e63b10c40afc2b3a9ab74357814e1
parent8089cd642fa8b29e852506218b6355be064c2bd5 (diff)
downloadcpython-a750ce3325b0daf6942834efd3644d1cf837abad.zip
cpython-a750ce3325b0daf6942834efd3644d1cf837abad.tar.gz
cpython-a750ce3325b0daf6942834efd3644d1cf837abad.tar.bz2
Issue #19105: pprint now more efficiently uses free space at the right.
-rw-r--r--Lib/pprint.py87
-rw-r--r--Lib/test/test_pprint.py97
-rw-r--r--Misc/NEWS2
3 files changed, 147 insertions, 39 deletions
diff --git a/Lib/pprint.py b/Lib/pprint.py
index 2cbffed..0091e69 100644
--- a/Lib/pprint.py
+++ b/Lib/pprint.py
@@ -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(),
diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py
index ad6a7a1..c056880 100644
--- a/Lib/test/test_pprint.py
+++ b/Lib/test/test_pprint.py
@@ -192,10 +192,52 @@ class QueryTestCase(unittest.TestCase):
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):
diff --git a/Misc/NEWS b/Misc/NEWS
index 27b58e3..9259d2c 100644
--- 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.