Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 64c16c3

Browse files
committed
Issue #17150: pprint now uses line continuations to wrap long string literals.
1 parent 4a8ea9e commit 64c16c3

4 files changed

Lines changed: 202 additions & 92 deletions

File tree

Doc/library/pprint.rst

Lines changed: 127 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ The :mod:`pprint` module provides a capability to "pretty-print" arbitrary
1414
Python data structures in a form which can be used as input to the interpreter.
1515
If the formatted structures include objects which are not fundamental Python
1616
types, the representation may not be loadable. This may be the case if objects
17-
such as files, sockets, classes, or instances are included, as well as many
18-
other built-in objects which are not representable as Python constants.
17+
such as files, sockets or classes are included, as well as many other
18+
objects which are not representable as Python literals.
1919

2020
The formatted representation keeps objects on a single line if it can, and
2121
breaks them onto multiple lines if they don't fit within the allowed width.
@@ -65,7 +65,7 @@ The :mod:`pprint` module defines one class:
6565
('spam', ('eggs', ('lumberjack', ('knights', ('ni', ('dead', (...)))))))
6666

6767

68-
The :class:`PrettyPrinter` class supports several derivative functions:
68+
The :mod:`pprint` module also provides several shortcut functions:
6969

7070
.. function:: pformat(object, indent=1, width=80, depth=None)
7171

@@ -193,101 +193,141 @@ Example
193193
-------
194194

195195
To demonstrate several uses of the :func:`pprint` function and its parameters,
196-
let's fetch information about a project from PyPI::
196+
let's fetch information about a project from `PyPI <https://pypi.python.org>`_::
197197

198198
>>> import json
199199
>>> import pprint
200200
>>> from urllib.request import urlopen
201-
>>> with urlopen('http://pypi.python.org/pypi/configparser/json') as url:
201+
>>> with urlopen('http://pypi.python.org/pypi/Twisted/json') as url:
202202
... http_info = url.info()
203203
... raw_data = url.read().decode(http_info.get_content_charset())
204204
>>> project_info = json.loads(raw_data)
205-
>>> result = {'headers': http_info.items(), 'body': project_info}
206205

207206
In its basic form, :func:`pprint` shows the whole object::
208207

209-
>>> pprint.pprint(result)
210-
{'body': {'info': {'_pypi_hidden': False,
211-
'_pypi_ordering': 12,
212-
'classifiers': ['Development Status :: 4 - Beta',
213-
'Intended Audience :: Developers',
214-
'License :: OSI Approved :: MIT License',
215-
'Natural Language :: English',
216-
'Operating System :: OS Independent',
217-
'Programming Language :: Python',
218-
'Programming Language :: Python :: 2',
219-
'Programming Language :: Python :: 2.6',
220-
'Programming Language :: Python :: 2.7',
221-
'Topic :: Software Development :: Libraries',
222-
'Topic :: Software Development :: Libraries :: Python Modules'],
223-
'download_url': 'UNKNOWN',
224-
'home_page': 'http://docs.python.org/py3k/library/configparser.html',
225-
'keywords': 'configparser ini parsing conf cfg configuration file',
226-
'license': 'MIT',
227-
'name': 'configparser',
228-
'package_url': 'http://pypi.python.org/pypi/configparser',
229-
'platform': 'any',
230-
'release_url': 'http://pypi.python.org/pypi/configparser/3.2.0r3',
231-
'requires_python': None,
232-
'stable_version': None,
233-
'summary': 'This library brings the updated configparser from Python 3.2+ to Python 2.6-2.7.',
234-
'version': '3.2.0r3'},
235-
'urls': [{'comment_text': '',
236-
'downloads': 47,
237-
'filename': 'configparser-3.2.0r3.tar.gz',
238-
'has_sig': False,
239-
'md5_digest': '8500fd87c61ac0de328fc996fce69b96',
240-
'packagetype': 'sdist',
241-
'python_version': 'source',
242-
'size': 32281,
243-
'upload_time': '2011-05-10T16:28:50',
244-
'url': 'http://pypi.python.org/packages/source/c/configparser/configparser-3.2.0r3.tar.gz'}]},
245-
'headers': [('Date', 'Sat, 14 May 2011 12:48:52 GMT'),
246-
('Server', 'Apache/2.2.16 (Debian)'),
247-
('Content-Disposition', 'inline'),
248-
('Connection', 'close'),
249-
('Transfer-Encoding', 'chunked'),
250-
('Content-Type', 'application/json; charset="UTF-8"')]}
208+
>>> pprint.pprint(project_info)
209+
{'info': {'_pypi_hidden': False,
210+
'_pypi_ordering': 125,
211+
'author': 'Glyph Lefkowitz',
212+
'author_email': '[email protected]',
213+
'bugtrack_url': '',
214+
'cheesecake_code_kwalitee_id': None,
215+
'cheesecake_documentation_id': None,
216+
'cheesecake_installability_id': None,
217+
'classifiers': ['Programming Language :: Python :: 2.6',
218+
'Programming Language :: Python :: 2.7',
219+
'Programming Language :: Python :: 2 :: Only'],
220+
'description': 'An extensible framework for Python programming, '
221+
'with special focus\r\n'
222+
'on event-based network programming and '
223+
'multiprotocol integration.',
224+
'docs_url': '',
225+
'download_url': 'UNKNOWN',
226+
'home_page': 'http://twistedmatrix.com/',
227+
'keywords': '',
228+
'license': 'MIT',
229+
'maintainer': '',
230+
'maintainer_email': '',
231+
'name': 'Twisted',
232+
'package_url': 'http://pypi.python.org/pypi/Twisted',
233+
'platform': 'UNKNOWN',
234+
'release_url': 'http://pypi.python.org/pypi/Twisted/12.3.0',
235+
'requires_python': None,
236+
'stable_version': None,
237+
'summary': 'An asynchronous networking framework written in Python',
238+
'version': '12.3.0'},
239+
'urls': [{'comment_text': '',
240+
'downloads': 71844,
241+
'filename': 'Twisted-12.3.0.tar.bz2',
242+
'has_sig': False,
243+
'md5_digest': '6e289825f3bf5591cfd670874cc0862d',
244+
'packagetype': 'sdist',
245+
'python_version': 'source',
246+
'size': 2615733,
247+
'upload_time': '2012-12-26T12:47:03',
248+
'url': 'https://pypi.python.org/packages/source/T/Twisted/Twisted-12.3.0.tar.bz2'},
249+
{'comment_text': '',
250+
'downloads': 5224,
251+
'filename': 'Twisted-12.3.0.win32-py2.7.msi',
252+
'has_sig': False,
253+
'md5_digest': '6b778f5201b622a5519a2aca1a2fe512',
254+
'packagetype': 'bdist_msi',
255+
'python_version': '2.7',
256+
'size': 2916352,
257+
'upload_time': '2012-12-26T12:48:15',
258+
'url': 'https://pypi.python.org/packages/2.7/T/Twisted/Twisted-12.3.0.win32-py2.7.msi'}]}
251259

252260
The result can be limited to a certain *depth* (ellipsis is used for deeper
253261
contents)::
254262

255-
>>> pprint.pprint(result, depth=3)
256-
{'body': {'info': {'_pypi_hidden': False,
257-
'_pypi_ordering': 12,
258-
'classifiers': [...],
259-
'download_url': 'UNKNOWN',
260-
'home_page': 'http://docs.python.org/py3k/library/configparser.html',
261-
'keywords': 'configparser ini parsing conf cfg configuration file',
262-
'license': 'MIT',
263-
'name': 'configparser',
264-
'package_url': 'http://pypi.python.org/pypi/configparser',
265-
'platform': 'any',
266-
'release_url': 'http://pypi.python.org/pypi/configparser/3.2.0r3',
267-
'requires_python': None,
268-
'stable_version': None,
269-
'summary': 'This library brings the updated configparser from Python 3.2+ to Python 2.6-2.7.',
270-
'version': '3.2.0r3'},
271-
'urls': [{...}]},
272-
'headers': [('Date', 'Sat, 14 May 2011 12:48:52 GMT'),
273-
('Server', 'Apache/2.2.16 (Debian)'),
274-
('Content-Disposition', 'inline'),
275-
('Connection', 'close'),
276-
('Transfer-Encoding', 'chunked'),
277-
('Content-Type', 'application/json; charset="UTF-8"')]}
278-
279-
Additionally, maximum *width* can be suggested. If a long object cannot be
280-
split, the specified width will be exceeded::
281-
282-
>>> pprint.pprint(result['headers'], width=30)
283-
[('Date',
284-
'Sat, 14 May 2011 12:48:52 GMT'),
285-
('Server',
286-
'Apache/2.2.16 (Debian)'),
287-
('Content-Disposition',
288-
'inline'),
289-
('Connection', 'close'),
290-
('Transfer-Encoding',
291-
'chunked'),
292-
('Content-Type',
293-
'application/json; charset="UTF-8"')]
263+
>>> pprint.pprint(project_info, depth=2)
264+
{'info': {'_pypi_hidden': False,
265+
'_pypi_ordering': 125,
266+
'author': 'Glyph Lefkowitz',
267+
'author_email': '[email protected]',
268+
'bugtrack_url': '',
269+
'cheesecake_code_kwalitee_id': None,
270+
'cheesecake_documentation_id': None,
271+
'cheesecake_installability_id': None,
272+
'classifiers': [...],
273+
'description': 'An extensible framework for Python programming, '
274+
'with special focus\r\n'
275+
'on event-based network programming and '
276+
'multiprotocol integration.',
277+
'docs_url': '',
278+
'download_url': 'UNKNOWN',
279+
'home_page': 'http://twistedmatrix.com/',
280+
'keywords': '',
281+
'license': 'MIT',
282+
'maintainer': '',
283+
'maintainer_email': '',
284+
'name': 'Twisted',
285+
'package_url': 'http://pypi.python.org/pypi/Twisted',
286+
'platform': 'UNKNOWN',
287+
'release_url': 'http://pypi.python.org/pypi/Twisted/12.3.0',
288+
'requires_python': None,
289+
'stable_version': None,
290+
'summary': 'An asynchronous networking framework written in Python',
291+
'version': '12.3.0'},
292+
'urls': [{...}, {...}]}
293+
294+
Additionally, maximum character *width* can be suggested. If a long object
295+
cannot be split, the specified width will be exceeded::
296+
297+
>>> pprint.pprint(project_info, depth=2, width=50)
298+
{'info': {'_pypi_hidden': False,
299+
'_pypi_ordering': 125,
300+
'author': 'Glyph Lefkowitz',
301+
'author_email': '[email protected]',
302+
'bugtrack_url': '',
303+
'cheesecake_code_kwalitee_id': None,
304+
'cheesecake_documentation_id': None,
305+
'cheesecake_installability_id': None,
306+
'classifiers': [...],
307+
'description': 'An extensible '
308+
'framework for '
309+
'Python programming, '
310+
'with special '
311+
'focus\r\n'
312+
'on event-based '
313+
'network programming '
314+
'and multiprotocol '
315+
'integration.',
316+
'docs_url': '',
317+
'download_url': 'UNKNOWN',
318+
'home_page': 'http://twistedmatrix.com/',
319+
'keywords': '',
320+
'license': 'MIT',
321+
'maintainer': '',
322+
'maintainer_email': '',
323+
'name': 'Twisted',
324+
'package_url': 'http://pypi.python.org/pypi/Twisted',
325+
'platform': 'UNKNOWN',
326+
'release_url': 'http://pypi.python.org/pypi/Twisted/12.3.0',
327+
'requires_python': None,
328+
'stable_version': None,
329+
'summary': 'An asynchronous '
330+
'networking framework '
331+
'written in Python',
332+
'version': '12.3.0'},
333+
'urls': [{...}, {...}]}

Lib/pprint.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
3535
"""
3636

37+
import re
3738
import sys as _sys
3839
from collections import OrderedDict as _OrderedDict
3940
from io import StringIO as _StringIO
@@ -158,13 +159,10 @@ def _format(self, object, stream, indent, allowance, context, level):
158159
return
159160
rep = self._repr(object, context, level - 1)
160161
typ = _type(object)
161-
sepLines = _len(rep) > (self._width - 1 - indent - allowance)
162+
max_width = self._width - 1 - indent - allowance
163+
sepLines = _len(rep) > max_width
162164
write = stream.write
163165

164-
if self._depth and level > self._depth:
165-
write(rep)
166-
return
167-
168166
if sepLines:
169167
r = getattr(typ, "__repr__", None)
170168
if issubclass(typ, dict):
@@ -242,6 +240,37 @@ def _format(self, object, stream, indent, allowance, context, level):
242240
write(endchar)
243241
return
244242

243+
if issubclass(typ, str) and len(object) > 0 and r is str.__repr__:
244+
def _str_parts(s):
245+
"""
246+
Return a list of string literals comprising the repr()
247+
of the given string using literal concatenation.
248+
"""
249+
lines = s.splitlines(True)
250+
for i, line in enumerate(lines):
251+
rep = repr(line)
252+
if _len(rep) <= max_width:
253+
yield rep
254+
else:
255+
# A list of alternating (non-space, space) strings
256+
parts = re.split(r'(\s+)', line) + ['']
257+
current = ''
258+
for i in range(0, len(parts), 2):
259+
part = parts[i] + parts[i+1]
260+
candidate = current + part
261+
if len(repr(candidate)) > max_width:
262+
if current:
263+
yield repr(current)
264+
current = part
265+
else:
266+
current = candidate
267+
if current:
268+
yield repr(current)
269+
for i, rep in enumerate(_str_parts(object)):
270+
if i > 0:
271+
write('\n' + ' '*indent)
272+
write(rep)
273+
return
245274
write(rep)
246275

247276
def _repr(self, object, context, level):

Lib/test/test_pprint.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# -*- coding: utf-8 -*-
2+
13
import pprint
24
import test.support
35
import unittest
@@ -475,6 +477,42 @@ def test_sort_unorderable_values(self):
475477
self.assertEqual(pprint.pformat(dict.fromkeys(keys, 0)),
476478
'{%r: 0, %r: 0}' % tuple(sorted(keys, key=id)))
477479

480+
def test_str_wrap(self):
481+
# pprint tries to wrap strings intelligently
482+
fox = 'the quick brown fox jumped over a lazy dog'
483+
self.assertEqual(pprint.pformat(fox, width=20), """\
484+
'the quick brown '
485+
'fox jumped over '
486+
'a lazy dog'""")
487+
self.assertEqual(pprint.pformat({'a': 1, 'b': fox, 'c': 2},
488+
width=26), """\
489+
{'a': 1,
490+
'b': 'the quick brown '
491+
'fox jumped over '
492+
'a lazy dog',
493+
'c': 2}""")
494+
# With some special characters
495+
# - \n always triggers a new line in the pprint
496+
# - \t and \n are escaped
497+
# - non-ASCII is allowed
498+
# - an apostrophe doesn't disrupt the pprint
499+
special = "Portons dix bons \"whiskys\"\nà l'avocat goujat\t qui fumait au zoo"
500+
self.assertEqual(pprint.pformat(special, width=20), """\
501+
'Portons dix bons '
502+
'"whiskys"\\n'
503+
"à l'avocat "
504+
'goujat\\t qui '
505+
'fumait au zoo'""")
506+
# An unwrappable string is formatted as its repr
507+
unwrappable = "x" * 100
508+
self.assertEqual(pprint.pformat(unwrappable, width=80), repr(unwrappable))
509+
self.assertEqual(pprint.pformat(''), "''")
510+
# Check that the pprint is a usable repr
511+
special *= 10
512+
for width in range(3, 40):
513+
formatted = pprint.pformat(special, width=width)
514+
self.assertEqual(eval("(" + formatted + ")"), special)
515+
478516

479517
class DottedPrettyPrinter(pprint.PrettyPrinter):
480518

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,9 @@ Core and Builtins
294294
Library
295295
-------
296296

297+
- Issue #17150: pprint now uses line continuations to wrap long string
298+
literals.
299+
297300
- Issue #17488: Change the subprocess.Popen bufsize parameter default value
298301
from unbuffered (0) to buffering (-1) to match the behavior existing code
299302
expects and match the behavior of the subprocess module in Python 2 to avoid

0 commit comments

Comments
 (0)