From 3f04477024b375f38f724767eb71822667ffc656 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 14 Apr 2026 23:00:58 +0300 Subject: [PATCH 1/8] Default pprint to expand=True indent=4 width=88 --- Doc/library/pprint.rst | 440 +++++++++--------- Doc/library/ssl.rst | 122 ++--- Doc/library/unittest.mock.rst | 10 +- Doc/tutorial/stdlib2.rst | 21 +- Doc/whatsnew/3.15.rst | 3 + Lib/difflib.py | 42 +- Lib/pprint.py | 44 +- Lib/test/test_descrtut.py | 98 ++-- Lib/test/test_pickle.py | 6 +- Lib/test/test_pprint.py | 407 ++++++++-------- Lib/test/test_stable_abi_ctypes.py | 28 +- .../test_unittest/testmock/testhelpers.py | 4 +- ...-04-30-18-56-23.gh-issue-149189.mszW10.rst | 2 + 13 files changed, 638 insertions(+), 589 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-04-30-18-56-23.gh-issue-149189.mszW10.rst diff --git a/Doc/library/pprint.rst b/Doc/library/pprint.rst index 4f043fbb3a46df..a7011e13b1a03b 100644 --- a/Doc/library/pprint.rst +++ b/Doc/library/pprint.rst @@ -17,7 +17,7 @@ objects which are not representable as Python literals. The formatted representation keeps objects on a single line if it can, and breaks them onto multiple lines if they don't fit within the allowed width, -adjustable by the *width* parameter defaulting to 80 characters. +adjustable by the *width* parameter defaulting to 88 characters. .. versionchanged:: 3.9 Added support for pretty-printing :class:`types.SimpleNamespace`. @@ -30,8 +30,8 @@ adjustable by the *width* parameter defaulting to 80 characters. Functions --------- -.. function:: pp(object, stream=None, indent=1, width=80, depth=None, *, \ - compact=False, expand=False, sort_dicts=False, \ +.. function:: pp(object, stream=None, indent=4, width=88, depth=None, *, \ + compact=False, expand=True, sort_dicts=False, \ underscore_numbers=False) Prints the formatted representation of *object*, followed by a newline. @@ -92,18 +92,13 @@ Functions >>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni'] >>> stuff.insert(0, stuff) >>> pprint.pp(stuff) - [, - 'spam', - 'eggs', - 'lumberjack', - 'knights', - 'ni'] + [, 'spam', 'eggs', 'lumberjack', 'knights', 'ni'] .. versionadded:: 3.8 -.. function:: pprint(object, stream=None, indent=1, width=80, depth=None, *, \ - compact=False, expand=False, sort_dicts=True, \ +.. function:: pprint(object, stream=None, indent=4, width=88, depth=None, *, \ + compact=False, expand=True, sort_dicts=True, \ underscore_numbers=False) Alias for :func:`~pprint.pp` with *sort_dicts* set to ``True`` by default, @@ -111,8 +106,8 @@ Functions you might want to use :func:`~pprint.pp` instead where it is ``False`` by default. -.. function:: pformat(object, indent=1, width=80, depth=None, *, \ - compact=False, expand=False, sort_dicts=True, \ +.. function:: pformat(object, indent=4, width=88, depth=None, *, \ + compact=False, expand=True, sort_dicts=True, \ underscore_numbers=False) Return the formatted representation of *object* as a string. *indent*, @@ -154,13 +149,13 @@ Functions .. _prettyprinter-objects: -PrettyPrinter Objects +PrettyPrinter objects --------------------- .. index:: single: ...; placeholder -.. class:: PrettyPrinter(indent=1, width=80, depth=None, stream=None, *, \ - compact=False, expand=False, sort_dicts=True, \ +.. class:: PrettyPrinter(indent=4, width=88, depth=None, stream=None, *, \ + compact=False, expand=True, sort_dicts=True, \ underscore_numbers=False) Construct a :class:`PrettyPrinter` instance. @@ -173,19 +168,21 @@ PrettyPrinter Objects >>> stuff.insert(0, stuff[:]) >>> pp = pprint.PrettyPrinter(indent=4) >>> pp.pprint(stuff) - [ ['spam', 'eggs', 'lumberjack', 'knights', 'ni'], + [ + ['spam', 'eggs', 'lumberjack', 'knights', 'ni'], 'spam', 'eggs', 'lumberjack', 'knights', - 'ni'] - >>> pp = pprint.PrettyPrinter(width=41, compact=True) + 'ni', + ] + >>> pp = pprint.PrettyPrinter(width=41, compact=True, expand=False) >>> pp.pprint(stuff) - [['spam', 'eggs', 'lumberjack', - 'knights', 'ni'], - 'spam', 'eggs', 'lumberjack', 'knights', - 'ni'] - >>> pp = pprint.PrettyPrinter(width=41, expand=True, indent=3) + [ [ 'spam', 'eggs', 'lumberjack', + 'knights', 'ni'], + 'spam', 'eggs', 'lumberjack', + 'knights', 'ni'] + >>> pp = pprint.PrettyPrinter(width=41, indent=3) >>> pp.pprint(stuff) [ [ @@ -223,6 +220,11 @@ PrettyPrinter Objects .. versionchanged:: 3.15 Added the *expand* parameter. + .. versionchanged:: next + Changed default *indent* from 1 to 4, + default *width* from 80 to 88, + and default *expand* from ``False`` to ``True``. + :class:`PrettyPrinter` instances have the following methods: @@ -298,219 +300,201 @@ let's fetch information about a project from `PyPI `_:: In its basic form, :func:`~pprint.pp` shows the whole object:: >>> pprint.pp(project_info) - {'author': 'The Python Packaging Authority', - 'author_email': 'pypa-dev@googlegroups.com', - 'bugtrack_url': None, - 'classifiers': ['Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Topic :: Software Development :: Build Tools'], - 'description': 'A sample Python project\n' - '=======================\n' - '\n' - 'This is the description file for the project.\n' - '\n' - 'The file should use UTF-8 encoding and be written using ' - 'ReStructured Text. It\n' - 'will be used to generate the project webpage on PyPI, and ' - 'should be written for\n' - 'that purpose.\n' - '\n' - 'Typical contents for this file would include an overview of ' - 'the project, basic\n' - 'usage examples, etc. Generally, including the project ' - 'changelog in here is not\n' - 'a good idea, although a simple "What\'s New" section for the ' - 'most recent version\n' - 'may be appropriate.', - 'description_content_type': None, - 'docs_url': None, - 'download_url': 'UNKNOWN', - 'downloads': {'last_day': -1, 'last_month': -1, 'last_week': -1}, - 'home_page': 'https://github.com/pypa/sampleproject', - 'keywords': 'sample setuptools development', - 'license': 'MIT', - 'maintainer': None, - 'maintainer_email': None, - 'name': 'sampleproject', - 'package_url': 'https://pypi.org/project/sampleproject/', - 'platform': 'UNKNOWN', - 'project_url': 'https://pypi.org/project/sampleproject/', - 'project_urls': {'Download': 'UNKNOWN', - 'Homepage': 'https://github.com/pypa/sampleproject'}, - 'release_url': 'https://pypi.org/project/sampleproject/1.2.0/', - 'requires_dist': None, - 'requires_python': None, - 'summary': 'A sample Python project', - 'version': '1.2.0'} + { + 'author': 'The Python Packaging Authority', + 'author_email': 'pypa-dev@googlegroups.com', + 'bugtrack_url': None, + 'classifiers': [ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Topic :: Software Development :: Build Tools', + ], + 'description': 'A sample Python project\n' + '=======================\n' + '\n' + 'This is the description file for the project.\n' + '\n' + 'The file should use UTF-8 encoding and be written using ReStructured Text. It\n' + 'will be used to generate the project webpage on PyPI, and should be written for\n' + 'that purpose.\n' + '\n' + 'Typical contents for this file would include an overview of the project, basic\n' + 'usage examples, etc. Generally, including the project changelog in here is not\n' + 'a good idea, although a simple "What\'s New" section for the most recent version\n' + 'may be appropriate.', + 'description_content_type': None, + 'docs_url': None, + 'download_url': 'UNKNOWN', + 'downloads': {'last_day': -1, 'last_month': -1, 'last_week': -1}, + 'home_page': 'https://github.com/pypa/sampleproject', + 'keywords': 'sample setuptools development', + 'license': 'MIT', + 'maintainer': None, + 'maintainer_email': None, + 'name': 'sampleproject', + 'package_url': 'https://pypi.org/project/sampleproject/', + 'platform': 'UNKNOWN', + 'project_url': 'https://pypi.org/project/sampleproject/', + 'project_urls': {'Download': 'UNKNOWN', 'Homepage': 'https://github.com/pypa/sampleproject'}, + 'release_url': 'https://pypi.org/project/sampleproject/1.2.0/', + 'requires_dist': None, + 'requires_python': None, + 'summary': 'A sample Python project', + 'version': '1.2.0', + } The result can be limited to a certain *depth* (ellipsis is used for deeper contents):: >>> pprint.pp(project_info, depth=1) - {'author': 'The Python Packaging Authority', - 'author_email': 'pypa-dev@googlegroups.com', - 'bugtrack_url': None, - 'classifiers': [...], - 'description': 'A sample Python project\n' - '=======================\n' - '\n' - 'This is the description file for the project.\n' - '\n' - 'The file should use UTF-8 encoding and be written using ' - 'ReStructured Text. It\n' - 'will be used to generate the project webpage on PyPI, and ' - 'should be written for\n' - 'that purpose.\n' - '\n' - 'Typical contents for this file would include an overview of ' - 'the project, basic\n' - 'usage examples, etc. Generally, including the project ' - 'changelog in here is not\n' - 'a good idea, although a simple "What\'s New" section for the ' - 'most recent version\n' - 'may be appropriate.', - 'description_content_type': None, - 'docs_url': None, - 'download_url': 'UNKNOWN', - 'downloads': {...}, - 'home_page': 'https://github.com/pypa/sampleproject', - 'keywords': 'sample setuptools development', - 'license': 'MIT', - 'maintainer': None, - 'maintainer_email': None, - 'name': 'sampleproject', - 'package_url': 'https://pypi.org/project/sampleproject/', - 'platform': 'UNKNOWN', - 'project_url': 'https://pypi.org/project/sampleproject/', - 'project_urls': {...}, - 'release_url': 'https://pypi.org/project/sampleproject/1.2.0/', - 'requires_dist': None, - 'requires_python': None, - 'summary': 'A sample Python project', - 'version': '1.2.0'} + { + 'author': 'The Python Packaging Authority', + 'author_email': 'pypa-dev@googlegroups.com', + 'bugtrack_url': None, + 'classifiers': [...], + 'description': 'A sample Python project\n' + '=======================\n' + '\n' + 'This is the description file for the project.\n' + '\n' + 'The file should use UTF-8 encoding and be written using ReStructured Text. It\n' + 'will be used to generate the project webpage on PyPI, and should be written for\n' + 'that purpose.\n' + '\n' + 'Typical contents for this file would include an overview of the project, basic\n' + 'usage examples, etc. Generally, including the project changelog in here is not\n' + 'a good idea, although a simple "What\'s New" section for the most recent version\n' + 'may be appropriate.', + 'description_content_type': None, + 'docs_url': None, + 'download_url': 'UNKNOWN', + 'downloads': {...}, + 'home_page': 'https://github.com/pypa/sampleproject', + 'keywords': 'sample setuptools development', + 'license': 'MIT', + 'maintainer': None, + 'maintainer_email': None, + 'name': 'sampleproject', + 'package_url': 'https://pypi.org/project/sampleproject/', + 'platform': 'UNKNOWN', + 'project_url': 'https://pypi.org/project/sampleproject/', + 'project_urls': {...}, + 'release_url': 'https://pypi.org/project/sampleproject/1.2.0/', + 'requires_dist': None, + 'requires_python': None, + 'summary': 'A sample Python project', + 'version': '1.2.0', + } Additionally, maximum character *width* can be suggested. If a long object cannot be split, the specified width will be exceeded:: >>> pprint.pp(project_info, depth=1, width=60) - {'author': 'The Python Packaging Authority', - 'author_email': 'pypa-dev@googlegroups.com', - 'bugtrack_url': None, - 'classifiers': [...], - 'description': 'A sample Python project\n' - '=======================\n' - '\n' - 'This is the description file for the ' - 'project.\n' - '\n' - 'The file should use UTF-8 encoding and be ' - 'written using ReStructured Text. It\n' - 'will be used to generate the project ' - 'webpage on PyPI, and should be written ' - 'for\n' - 'that purpose.\n' - '\n' - 'Typical contents for this file would ' - 'include an overview of the project, ' - 'basic\n' - 'usage examples, etc. Generally, including ' - 'the project changelog in here is not\n' - 'a good idea, although a simple "What\'s ' - 'New" section for the most recent version\n' - 'may be appropriate.', - 'description_content_type': None, - 'docs_url': None, - 'download_url': 'UNKNOWN', - 'downloads': {...}, - 'home_page': 'https://github.com/pypa/sampleproject', - 'keywords': 'sample setuptools development', - 'license': 'MIT', - 'maintainer': None, - 'maintainer_email': None, - 'name': 'sampleproject', - 'package_url': 'https://pypi.org/project/sampleproject/', - 'platform': 'UNKNOWN', - 'project_url': 'https://pypi.org/project/sampleproject/', - 'project_urls': {...}, - 'release_url': 'https://pypi.org/project/sampleproject/1.2.0/', - 'requires_dist': None, - 'requires_python': None, - 'summary': 'A sample Python project', - 'version': '1.2.0'} - -Lastly, we can format like pretty-printed JSON with the *expand* parameter. -Best results are achieved with a higher *indent* value:: - - >>> pprint.pp(project_info, indent=4, expand=True) { - 'author': 'The Python Packaging Authority', - 'author_email': 'pypa-dev@googlegroups.com', - 'bugtrack_url': None, - 'classifiers': [ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Topic :: Software Development :: Build Tools', - ], - 'description': 'A sample Python project\n' - '=======================\n' - '\n' - 'This is the description file for the project.\n' - '\n' - 'The file should use UTF-8 encoding and be written using ReStructured ' - 'Text. It\n' - 'will be used to generate the project webpage on PyPI, and should be ' - 'written for\n' - 'that purpose.\n' - '\n' - 'Typical contents for this file would include an overview of the project, ' - 'basic\n' - 'usage examples, etc. Generally, including the project changelog in here ' - 'is not\n' - 'a good idea, although a simple "What\'s New" section for the most recent ' - 'version\n' - 'may be appropriate.', - 'description_content_type': None, - 'docs_url': None, - 'download_url': 'UNKNOWN', - 'downloads': {'last_day': -1, 'last_month': -1, 'last_week': -1}, - 'dynamic': None, - 'home_page': 'https://github.com/pypa/sampleproject', - 'keywords': 'sample setuptools development', - 'license': 'MIT', - 'license_expression': None, - 'license_files': None, - 'maintainer': None, - 'maintainer_email': None, - 'name': 'sampleproject', - 'package_url': 'https://pypi.org/project/sampleproject/', - 'platform': 'UNKNOWN', - 'project_url': 'https://pypi.org/project/sampleproject/', - 'project_urls': { - 'Download': 'UNKNOWN', - 'Homepage': 'https://github.com/pypa/sampleproject', - }, - 'provides_extra': None, - 'release_url': 'https://pypi.org/project/sampleproject/1.2.0/', - 'requires_dist': None, - 'requires_python': None, - 'summary': 'A sample Python project', - 'version': '1.2.0', - 'yanked': False, - 'yanked_reason': None, + 'author': 'The Python Packaging Authority', + 'author_email': 'pypa-dev@googlegroups.com', + 'bugtrack_url': None, + 'classifiers': [...], + 'description': 'A sample Python project\n' + '=======================\n' + '\n' + 'This is the description file for the project.\n' + '\n' + 'The file should use UTF-8 encoding and be written ' + 'using ReStructured Text. It\n' + 'will be used to generate the project webpage on PyPI, ' + 'and should be written for\n' + 'that purpose.\n' + '\n' + 'Typical contents for this file would include an ' + 'overview of the project, basic\n' + 'usage examples, etc. Generally, including the project ' + 'changelog in here is not\n' + 'a good idea, although a simple "What\'s New" section ' + 'for the most recent version\n' + 'may be appropriate.', + 'description_content_type': None, + 'docs_url': None, + 'download_url': 'UNKNOWN', + 'downloads': {...}, + 'home_page': 'https://github.com/pypa/sampleproject', + 'keywords': 'sample setuptools development', + 'license': 'MIT', + 'maintainer': None, + 'maintainer_email': None, + 'name': 'sampleproject', + 'package_url': 'https://pypi.org/project/sampleproject/', + 'platform': 'UNKNOWN', + 'project_url': 'https://pypi.org/project/sampleproject/', + 'project_urls': {...}, + 'release_url': 'https://pypi.org/project/sampleproject/1.2.0/', + 'requires_dist': None, + 'requires_python': None, + 'summary': 'A sample Python project', + 'version': '1.2.0', } + +The *expand* format (similar to pretty-printed JSON) is the default. +To disable it, pass ``expand=False``:: + + >>> pprint.pp(project_info, expand=False) + { 'author': 'The Python Packaging Authority', + 'author_email': 'pypa-dev@googlegroups.com', + 'bugtrack_url': None, + 'classifiers': [ 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Topic :: Software Development :: Build Tools'], + 'description': 'A sample Python project\n' + '=======================\n' + '\n' + 'This is the description file for the project.\n' + '\n' + 'The file should use UTF-8 encoding and be written using ' + 'ReStructured Text. It\n' + 'will be used to generate the project webpage on PyPI, and should ' + 'be written for\n' + 'that purpose.\n' + '\n' + 'Typical contents for this file would include an overview of the ' + 'project, basic\n' + 'usage examples, etc. Generally, including the project changelog in ' + 'here is not\n' + 'a good idea, although a simple "What\'s New" section for the most ' + 'recent version\n' + 'may be appropriate.', + 'description_content_type': None, + 'docs_url': None, + 'download_url': 'UNKNOWN', + 'downloads': {'last_day': -1, 'last_month': -1, 'last_week': -1}, + 'home_page': 'https://github.com/pypa/sampleproject', + 'keywords': 'sample setuptools development', + 'license': 'MIT', + 'maintainer': None, + 'maintainer_email': None, + 'name': 'sampleproject', + 'package_url': 'https://pypi.org/project/sampleproject/', + 'platform': 'UNKNOWN', + 'project_url': 'https://pypi.org/project/sampleproject/', + 'project_urls': { 'Download': 'UNKNOWN', + 'Homepage': 'https://github.com/pypa/sampleproject'}, + 'release_url': 'https://pypi.org/project/sampleproject/1.2.0/', + 'requires_dist': None, + 'requires_python': None, + 'summary': 'A sample Python project', + 'version': '1.2.0'} diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index f2c35d1897a77f..d9c736d27dcaec 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -2473,67 +2473,79 @@ Visual inspection shows that the certificate does identify the desired service (that is, the HTTPS host ``www.python.org``):: >>> pprint.pprint(cert) - {'OCSP': ('http://ocsp.digicert.com',), - 'caIssuers': ('http://cacerts.digicert.com/DigiCertSHA2ExtendedValidationServerCA.crt',), - 'crlDistributionPoints': ('http://crl3.digicert.com/sha2-ev-server-g1.crl', - 'http://crl4.digicert.com/sha2-ev-server-g1.crl'), - 'issuer': ((('countryName', 'US'),), - (('organizationName', 'DigiCert Inc'),), - (('organizationalUnitName', 'www.digicert.com'),), - (('commonName', 'DigiCert SHA2 Extended Validation Server CA'),)), - 'notAfter': 'Sep 9 12:00:00 2016 GMT', - 'notBefore': 'Sep 5 00:00:00 2014 GMT', - 'serialNumber': '01BB6F00122B177F36CAB49CEA8B6B26', - 'subject': ((('businessCategory', 'Private Organization'),), - (('1.3.6.1.4.1.311.60.2.1.3', 'US'),), - (('1.3.6.1.4.1.311.60.2.1.2', 'Delaware'),), - (('serialNumber', '3359300'),), - (('streetAddress', '16 Allen Rd'),), - (('postalCode', '03894-4801'),), - (('countryName', 'US'),), - (('stateOrProvinceName', 'NH'),), - (('localityName', 'Wolfeboro'),), - (('organizationName', 'Python Software Foundation'),), - (('commonName', 'www.python.org'),)), - 'subjectAltName': (('DNS', 'www.python.org'), - ('DNS', 'python.org'), - ('DNS', 'pypi.org'), - ('DNS', 'docs.python.org'), - ('DNS', 'testpypi.org'), - ('DNS', 'bugs.python.org'), - ('DNS', 'wiki.python.org'), - ('DNS', 'hg.python.org'), - ('DNS', 'mail.python.org'), - ('DNS', 'packaging.python.org'), - ('DNS', 'pythonhosted.org'), - ('DNS', 'www.pythonhosted.org'), - ('DNS', 'test.pythonhosted.org'), - ('DNS', 'us.pycon.org'), - ('DNS', 'id.python.org')), - 'version': 3} + { + 'OCSP': ('http://ocsp.digicert.com',), + 'caIssuers': ('http://cacerts.digicert.com/DigiCertSHA2ExtendedValidationServerCA.crt',), + 'crlDistributionPoints': ( + 'http://crl3.digicert.com/sha2-ev-server-g1.crl', + 'http://crl4.digicert.com/sha2-ev-server-g1.crl', + ), + 'issuer': ( + (('countryName', 'US'),), + (('organizationName', 'DigiCert Inc'),), + (('organizationalUnitName', 'www.digicert.com'),), + (('commonName', 'DigiCert SHA2 Extended Validation Server CA'),), + ), + 'notAfter': 'Sep 9 12:00:00 2016 GMT', + 'notBefore': 'Sep 5 00:00:00 2014 GMT', + 'serialNumber': '01BB6F00122B177F36CAB49CEA8B6B26', + 'subject': ( + (('businessCategory', 'Private Organization'),), + (('1.3.6.1.4.1.311.60.2.1.3', 'US'),), + (('1.3.6.1.4.1.311.60.2.1.2', 'Delaware'),), + (('serialNumber', '3359300'),), + (('streetAddress', '16 Allen Rd'),), + (('postalCode', '03894-4801'),), + (('countryName', 'US'),), + (('stateOrProvinceName', 'NH'),), + (('localityName', 'Wolfeboro'),), + (('organizationName', 'Python Software Foundation'),), + (('commonName', 'www.python.org'),), + ), + 'subjectAltName': ( + ('DNS', 'www.python.org'), + ('DNS', 'python.org'), + ('DNS', 'pypi.org'), + ('DNS', 'docs.python.org'), + ('DNS', 'testpypi.org'), + ('DNS', 'bugs.python.org'), + ('DNS', 'wiki.python.org'), + ('DNS', 'hg.python.org'), + ('DNS', 'mail.python.org'), + ('DNS', 'packaging.python.org'), + ('DNS', 'pythonhosted.org'), + ('DNS', 'www.pythonhosted.org'), + ('DNS', 'test.pythonhosted.org'), + ('DNS', 'us.pycon.org'), + ('DNS', 'id.python.org'), + ), + 'version': 3, + } Now the SSL channel is established and the certificate verified, you can proceed to talk with the server:: >>> conn.sendall(b"HEAD / HTTP/1.0\r\nHost: linuxfr.org\r\n\r\n") >>> pprint.pprint(conn.recv(1024).split(b"\r\n")) - [b'HTTP/1.1 200 OK', - b'Date: Sat, 18 Oct 2014 18:27:20 GMT', - b'Server: nginx', - b'Content-Type: text/html; charset=utf-8', - b'X-Frame-Options: SAMEORIGIN', - b'Content-Length: 45679', - b'Accept-Ranges: bytes', - b'Via: 1.1 varnish', - b'Age: 2188', - b'X-Served-By: cache-lcy1134-LCY', - b'X-Cache: HIT', - b'X-Cache-Hits: 11', - b'Vary: Cookie', - b'Strict-Transport-Security: max-age=63072000; includeSubDomains', - b'Connection: close', - b'', - b''] + [ + b'HTTP/1.1 200 OK', + b'Date: Sat, 18 Oct 2014 18:27:20 GMT', + b'Server: nginx', + b'Content-Type: text/html; charset=utf-8', + b'X-Frame-Options: SAMEORIGIN', + b'Content-Length: 45679', + b'Accept-Ranges: bytes', + b'Via: 1.1 varnish', + b'Age: 2188', + b'X-Served-By: cache-lcy1134-LCY', + b'X-Cache: HIT', + b'X-Cache-Hits: 11', + b'Vary: Cookie', + b'Strict-Transport-Security: max-age=63072000; includeSubDomains', + b'Connection: close', + b'', + b'', + ] See the discussion of :ref:`ssl-security` below. diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index 2ff1015af7a86e..5b9f9eec93aa28 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -2347,10 +2347,12 @@ chained call: >>> kall = call(1).method(arg='foo').other('bar')(2.0) >>> kall.call_list() - [call(1), - call().method(arg='foo'), - call().method().other('bar'), - call().method().other()(2.0)] + [ + call(1), + call().method(arg='foo'), + call().method().other('bar'), + call().method().other()(2.0), + ] >>> m.mock_calls == kall.call_list() True diff --git a/Doc/tutorial/stdlib2.rst b/Doc/tutorial/stdlib2.rst index 6c68ba01081379..2c3ec71cd3de39 100644 --- a/Doc/tutorial/stdlib2.rst +++ b/Doc/tutorial/stdlib2.rst @@ -30,11 +30,22 @@ and indentation to more clearly reveal data structure:: ... 'yellow'], 'blue']]] ... >>> pprint.pprint(t, width=30) - [[[['black', 'cyan'], - 'white', - ['green', 'red']], - [['magenta', 'yellow'], - 'blue']]] + [ + [ + [ + ['black', 'cyan'], + 'white', + ['green', 'red'], + ], + [ + [ + 'magenta', + 'yellow', + ], + 'blue', + ], + ], + ] The :mod:`textwrap` module formats paragraphs of text to fit a given screen width:: diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 90d24bf96afeb4..1d023a90adc456 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1094,6 +1094,9 @@ pprint (Contributed by Stefan Todoran, Semyon Moroz and Hugo van Kemenade in :gh:`112632`.) +* :mod:`pprint` now uses modern defaults: ``indent=4, width=88, expand=True``. + (Contributed by Hugo van Kemenade in :gh:`149189`.) + * Add t-string support to :mod:`pprint`. (Contributed by Loïc Simon and Hugo van Kemenade in :gh:`134551`.) diff --git a/Lib/difflib.py b/Lib/difflib.py index 8f3cdaed9564d8..eb249e3e288923 100644 --- a/Lib/difflib.py +++ b/Lib/difflib.py @@ -559,15 +559,17 @@ def get_grouped_opcodes(self, n=3): >>> b[23:28] = [] # Make a deletion >>> b[30] += 'y' # Make another replacement >>> pprint(list(SequenceMatcher(None,a,b).get_grouped_opcodes())) - [[('equal', 5, 8, 5, 8), ('insert', 8, 8, 8, 9), ('equal', 8, 11, 9, 12)], - [('equal', 16, 19, 17, 20), - ('replace', 19, 20, 20, 21), - ('equal', 20, 22, 21, 23), - ('delete', 22, 27, 23, 23), - ('equal', 27, 30, 23, 26)], - [('equal', 31, 34, 27, 30), - ('replace', 34, 35, 30, 31), - ('equal', 35, 38, 31, 34)]] + [ + [('equal', 5, 8, 5, 8), ('insert', 8, 8, 8, 9), ('equal', 8, 11, 9, 12)], + [ + ('equal', 16, 19, 17, 20), + ('replace', 19, 20, 20, 21), + ('equal', 20, 22, 21, 23), + ('delete', 22, 27, 23, 23), + ('equal', 27, 30, 23, 26), + ], + [('equal', 31, 34, 27, 30), ('replace', 34, 35, 30, 31), ('equal', 35, 38, 31, 34)], + ] """ codes = self.get_opcodes() @@ -784,16 +786,18 @@ class Differ: >>> from pprint import pprint as _pprint >>> _pprint(result) - [' 1. Beautiful is better than ugly.\n', - '- 2. Explicit is better than implicit.\n', - '- 3. Simple is better than complex.\n', - '+ 3. Simple is better than complex.\n', - '? ++\n', - '- 4. Complex is better than complicated.\n', - '? ^ ---- ^\n', - '+ 4. Complicated is better than complex.\n', - '? ++++ ^ ^\n', - '+ 5. Flat is better than nested.\n'] + [ + ' 1. Beautiful is better than ugly.\n', + '- 2. Explicit is better than implicit.\n', + '- 3. Simple is better than complex.\n', + '+ 3. Simple is better than complex.\n', + '? ++\n', + '- 4. Complex is better than complicated.\n', + '? ^ ---- ^\n', + '+ 4. Complicated is better than complex.\n', + '? ++++ ^ ^\n', + '+ 5. Flat is better than nested.\n', + ] As a single multi-line string it looks like this: diff --git a/Lib/pprint.py b/Lib/pprint.py index 7355021998081d..a94a333a06d6cb 100644 --- a/Lib/pprint.py +++ b/Lib/pprint.py @@ -43,9 +43,18 @@ "PrettyPrinter", "pp"] -def pprint(object, stream=None, indent=1, width=80, depth=None, *, - compact=False, expand=False, sort_dicts=True, - underscore_numbers=False): +def pprint( + object, + stream=None, + indent=4, + width=88, + depth=None, + *, + compact=False, + expand=True, + sort_dicts=True, + underscore_numbers=False, +): """Pretty-print a Python object to a stream [default is sys.stdout].""" printer = PrettyPrinter( stream=stream, indent=indent, width=width, depth=depth, @@ -54,9 +63,17 @@ def pprint(object, stream=None, indent=1, width=80, depth=None, *, printer.pprint(object) -def pformat(object, indent=1, width=80, depth=None, *, - compact=False, expand=False, sort_dicts=True, - underscore_numbers=False): +def pformat( + object, + indent=4, + width=88, + depth=None, + *, + compact=False, + expand=True, + sort_dicts=True, + underscore_numbers=False, +): """Format a Python object into a pretty-printed representation.""" return PrettyPrinter(indent=indent, width=width, depth=depth, compact=compact, expand=expand, sort_dicts=sort_dicts, @@ -112,9 +129,18 @@ def _safe_tuple(t): class PrettyPrinter: - def __init__(self, indent=1, width=80, depth=None, stream=None, *, - compact=False, expand=False, sort_dicts=True, - underscore_numbers=False): + def __init__( + self, + indent=4, + width=88, + depth=None, + stream=None, + *, + compact=False, + expand=True, + sort_dicts=True, + underscore_numbers=False, + ): """Handle pretty printing operations onto a stream using a set of configured parameters. diff --git a/Lib/test/test_descrtut.py b/Lib/test/test_descrtut.py index 828440a993a975..425fb85e93558d 100644 --- a/Lib/test/test_descrtut.py +++ b/Lib/test/test_descrtut.py @@ -168,54 +168,56 @@ def merge(self, other): >>> import pprint >>> pprint.pprint(dir(list)) # like list.__dict__.keys(), but sorted - ['__add__', - '__class__', - '__class_getitem__', - '__contains__', - '__delattr__', - '__delitem__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__getstate__', - '__gt__', - '__hash__', - '__iadd__', - '__imul__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__mul__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__reversed__', - '__rmul__', - '__setattr__', - '__setitem__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'append', - 'clear', - 'copy', - 'count', - 'extend', - 'index', - 'insert', - 'pop', - 'remove', - 'reverse', - 'sort'] + [ + '__add__', + '__class__', + '__class_getitem__', + '__contains__', + '__delattr__', + '__delitem__', + '__dir__', + '__doc__', + '__eq__', + '__format__', + '__ge__', + '__getattribute__', + '__getitem__', + '__getstate__', + '__gt__', + '__hash__', + '__iadd__', + '__imul__', + '__init__', + '__init_subclass__', + '__iter__', + '__le__', + '__len__', + '__lt__', + '__mul__', + '__ne__', + '__new__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__reversed__', + '__rmul__', + '__setattr__', + '__setitem__', + '__sizeof__', + '__str__', + '__subclasshook__', + 'append', + 'clear', + 'copy', + 'count', + 'extend', + 'index', + 'insert', + 'pop', + 'remove', + 'reverse', + 'sort', + ] The new introspection API gives more information than the old one: in addition to the regular methods, it also shows the methods that are diff --git a/Lib/test/test_pickle.py b/Lib/test/test_pickle.py index 48375cf459ea0b..55a3c654aa0a47 100644 --- a/Lib/test/test_pickle.py +++ b/Lib/test/test_pickle.py @@ -786,11 +786,7 @@ def test_invocation(self): 'b': ('character string', b'byte string'), 'c': 'string' } - expect = ''' - {'a': [1, 2.0, (3+4j)], - 'b': ('character string', b'byte string'), - 'c': 'string'} - ''' + expect = "{'a': [1, 2.0, (3+4j)], 'b': ('character string', b'byte string'), 'c': 'string'}" self.set_pickle_data(data) with self.subTest(data=data): diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py index 041c2072b9e253..6a4e33d82f5b2e 100644 --- a/Lib/test/test_pprint.py +++ b/Lib/test/test_pprint.py @@ -3,6 +3,7 @@ import collections import contextlib import dataclasses +import functools import io import itertools import pprint @@ -15,6 +16,9 @@ from test.support import cpython_only from test.support.import_helper import ensure_lazy_imports +# Pin pre-3.15 defaults for existing formatting tests +_pformat = functools.partial(pprint.pformat, indent=1, width=80, expand=False) + # list, tuple and dict subclasses that do or don't overwrite __repr__ class list2(list): pass @@ -155,7 +159,8 @@ def test_lazy_import(self): def test_init(self): pp = pprint.PrettyPrinter() pp = pprint.PrettyPrinter(indent=4, width=40, depth=5, - stream=io.StringIO(), compact=True) + stream=io.StringIO(), compact=True, + expand=False) pp = pprint.PrettyPrinter(4, 40, 5, io.StringIO()) pp = pprint.PrettyPrinter(sort_dicts=False) with self.assertRaises(TypeError): @@ -284,10 +289,10 @@ def test_same_as_repr(self): True, False, None, ..., ): native = repr(simple) - self.assertEqual(pprint.pformat(simple), native) - self.assertEqual(pprint.pformat(simple, width=1, indent=0) + self.assertEqual(_pformat(simple), native) + self.assertEqual(_pformat(simple, width=1, indent=0) .replace('\n', ' '), native) - self.assertEqual(pprint.pformat(simple, underscore_numbers=True), native) + self.assertEqual(_pformat(simple, underscore_numbers=True), native) self.assertEqual(pprint.saferepr(simple), native) def test_container_repr_override_called(self): @@ -318,8 +323,8 @@ def test_container_repr_override_called(self): ): native = repr(cont) expected = '*' * len(native) - self.assertEqual(pprint.pformat(cont), expected) - self.assertEqual(pprint.pformat(cont, width=1, indent=0), expected) + self.assertEqual(_pformat(cont), expected) + self.assertEqual(_pformat(cont, width=1, indent=0), expected) self.assertEqual(pprint.saferepr(cont), expected) def test_basic_line_wrap(self): @@ -340,7 +345,7 @@ def test_basic_line_wrap(self): 'read_io_runtime_us': 0, 'write_io_runtime_us': 43690}""" for type in [dict, dict2]: - self.assertEqual(pprint.pformat(type(o)), exp) + self.assertEqual(_pformat(type(o)), exp) exp = """\ frozendict({'RPM_cal': 0, @@ -350,7 +355,7 @@ def test_basic_line_wrap(self): 'main_code_runtime_us': 0, 'read_io_runtime_us': 0, 'write_io_runtime_us': 43690})""" - self.assertEqual(pprint.pformat(frozendict(o)), exp) + self.assertEqual(_pformat(frozendict(o)), exp) exp = """\ frozendict2({'RPM_cal': 0, 'RPM_cal2': 48059, @@ -359,79 +364,79 @@ def test_basic_line_wrap(self): 'main_code_runtime_us': 0, 'read_io_runtime_us': 0, 'write_io_runtime_us': 43690})""" - self.assertEqual(pprint.pformat(frozendict2(o)), exp) + self.assertEqual(_pformat(frozendict2(o)), exp) o = range(100) exp = 'dict_keys([%s])' % ',\n '.join(map(str, o)) keys = dict.fromkeys(o).keys() - self.assertEqual(pprint.pformat(keys), exp) + self.assertEqual(_pformat(keys), exp) keys = frozendict.fromkeys(o).keys() - self.assertEqual(pprint.pformat(keys), exp) + self.assertEqual(_pformat(keys), exp) o = range(100) exp = 'dict_values([%s])' % ',\n '.join(map(str, o)) values = {v: v for v in o}.values() - self.assertEqual(pprint.pformat(values), exp) + self.assertEqual(_pformat(values), exp) values = frozendict({v: v for v in o}).values() - self.assertEqual(pprint.pformat(values), exp) + self.assertEqual(_pformat(values), exp) o = range(100) exp = 'dict_items([%s])' % ',\n '.join("(%s, %s)" % (i, i) for i in o) items = {v: v for v in o}.items() - self.assertEqual(pprint.pformat(items), exp) + self.assertEqual(_pformat(items), exp) items = frozendict({v: v for v in o}).items() - self.assertEqual(pprint.pformat(items), exp) + self.assertEqual(_pformat(items), exp) o = range(100) exp = 'odict_keys([%s])' % ',\n '.join(map(str, o)) keys = collections.OrderedDict.fromkeys(o).keys() - self.assertEqual(pprint.pformat(keys), exp) + self.assertEqual(_pformat(keys), exp) o = range(100) exp = 'odict_values([%s])' % ',\n '.join(map(str, o)) values = collections.OrderedDict({v: v for v in o}).values() - self.assertEqual(pprint.pformat(values), exp) + self.assertEqual(_pformat(values), exp) o = range(100) exp = 'odict_items([%s])' % ',\n '.join("(%s, %s)" % (i, i) for i in o) items = collections.OrderedDict({v: v for v in o}).items() - self.assertEqual(pprint.pformat(items), exp) + self.assertEqual(_pformat(items), exp) o = range(100) exp = 'KeysView({%s})' % (': None,\n '.join(map(str, o)) + ': None') keys_view = KeysView(dict.fromkeys(o)) - self.assertEqual(pprint.pformat(keys_view), exp) + self.assertEqual(_pformat(keys_view), exp) o = range(100) exp = 'ItemsView({%s})' % (': None,\n '.join(map(str, o)) + ': None') items_view = ItemsView(dict.fromkeys(o)) - self.assertEqual(pprint.pformat(items_view), exp) + self.assertEqual(_pformat(items_view), exp) o = range(100) exp = 'MappingView({%s})' % (': None,\n '.join(map(str, o)) + ': None') mapping_view = MappingView(dict.fromkeys(o)) - self.assertEqual(pprint.pformat(mapping_view), exp) + self.assertEqual(_pformat(mapping_view), exp) o = range(100) exp = 'ValuesView({%s})' % (': None,\n '.join(map(str, o)) + ': None') values_view = ValuesView(dict.fromkeys(o)) - self.assertEqual(pprint.pformat(values_view), exp) + self.assertEqual(_pformat(values_view), exp) o = range(100) exp = '[%s]' % ',\n '.join(map(str, o)) for type in [list, list2]: - self.assertEqual(pprint.pformat(type(o)), exp) + self.assertEqual(_pformat(type(o)), exp) o = tuple(range(100)) exp = '(%s)' % ',\n '.join(map(str, o)) for type in [tuple, tuple2]: - self.assertEqual(pprint.pformat(type(o)), exp) + self.assertEqual(_pformat(type(o)), exp) # indent parameter o = range(100) exp = '[ %s]' % ',\n '.join(map(str, o)) for type in [list, list2]: - self.assertEqual(pprint.pformat(type(o), indent=4), exp) + self.assertEqual(_pformat(type(o), indent=4), exp) def test_nested_indentations(self): o1 = list(range(10)) @@ -440,13 +445,13 @@ def test_nested_indentations(self): 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=42, expand=False), 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=41), expected) + self.assertEqual(pprint.pformat(o, indent=4, width=41, expand=False), expected) def test_width(self): expected = """\ @@ -460,10 +465,10 @@ def test_width(self): [[[[[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), """\ + self.assertEqual(_pformat(o, width=15), expected) + self.assertEqual(_pformat(o, width=16), expected) + self.assertEqual(_pformat(o, width=25), expected) + self.assertEqual(_pformat(o, width=14), """\ [[[[[[1, 2, 3], @@ -487,8 +492,8 @@ def test_width(self): '2']]]]]""") def test_integer(self): - self.assertEqual(pprint.pformat(1234567), '1234567') - self.assertEqual(pprint.pformat(1234567, underscore_numbers=True), '1_234_567') + self.assertEqual(_pformat(1234567), '1234567') + self.assertEqual(_pformat(1234567, underscore_numbers=True), '1_234_567') class Temperature(int): def __new__(cls, celsius_degrees): @@ -496,7 +501,7 @@ def __new__(cls, celsius_degrees): def __repr__(self): kelvin_degrees = self + 273.15 return f"{kelvin_degrees:.2f}°K" - self.assertEqual(pprint.pformat(Temperature(1000)), '1273.15°K') + self.assertEqual(_pformat(Temperature(1000)), '1273.15°K') def test_sorted_dict(self): # Starting in Python 2.5, pprint sorts dict displays by key regardless @@ -504,8 +509,8 @@ def test_sorted_dict(self): # Before the change, on 32-bit Windows pformat() gave order # 'a', 'c', 'b' here, so this test failed. d = {'a': 1, 'b': 1, 'c': 1} - self.assertEqual(pprint.pformat(d), "{'a': 1, 'b': 1, 'c': 1}") - self.assertEqual(pprint.pformat([d, d]), + self.assertEqual(_pformat(d), "{'a': 1, 'b': 1, 'c': 1}") + self.assertEqual(_pformat([d, d]), "[{'a': 1, 'b': 1, 'c': 1}, {'a': 1, 'b': 1, 'c': 1}]") # The next one is kind of goofy. The sorted order depends on the @@ -513,23 +518,23 @@ def test_sorted_dict(self): # Python 2.5, this was in the test_same_as_repr() test. It's worth # keeping around for now because it's one of few tests of pprint # against a crazy mix of types. - self.assertEqual(pprint.pformat({"xy\tab\n": (3,), 5: [[]], (): {}}), + self.assertEqual(_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), + self.assertEqual(_pformat(d, sort_dicts=False), "{'c': None, 'b': None, 'a': None}") + self.assertEqual(_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()') + self.assertEqual(_pformat(d, width=1), 'OrderedDict()') d = collections.OrderedDict([]) - self.assertEqual(pprint.pformat(d, width=1), 'OrderedDict()') + self.assertEqual(_pformat(d, width=1), 'OrderedDict()') words = 'the quick brown fox jumped over a lazy dog'.split() d = collections.OrderedDict(zip(words, itertools.count())) - self.assertEqual(pprint.pformat(d), + self.assertEqual(_pformat(d), """\ OrderedDict([('the', 0), ('quick', 1), @@ -540,7 +545,7 @@ def test_ordered_dict(self): ('a', 6), ('lazy', 7), ('dog', 8)])""") - self.assertEqual(pprint.pformat(d.keys(), sort_dicts=False), + self.assertEqual(_pformat(d.keys(), sort_dicts=False), """\ odict_keys(['the', 'quick', @@ -551,7 +556,7 @@ def test_ordered_dict(self): 'a', 'lazy', 'dog'])""") - self.assertEqual(pprint.pformat(d.items(), sort_dicts=False), + self.assertEqual(_pformat(d.items(), sort_dicts=False), """\ odict_items([('the', 0), ('quick', 1), @@ -562,14 +567,14 @@ def test_ordered_dict(self): ('a', 6), ('lazy', 7), ('dog', 8)])""") - self.assertEqual(pprint.pformat(d.values(), sort_dicts=False), + self.assertEqual(_pformat(d.values(), sort_dicts=False), "odict_values([0, 1, 2, 3, 4, 5, 6, 7, 8])") def test_mapping_proxy(self): words = 'the quick brown fox jumped over a lazy dog'.split() d = dict(zip(words, itertools.count())) m = types.MappingProxyType(d) - self.assertEqual(pprint.pformat(m), """\ + self.assertEqual(_pformat(m), """\ mappingproxy({'a': 6, 'brown': 2, 'dog': 8, @@ -581,7 +586,7 @@ def test_mapping_proxy(self): 'the': 0})""") d = collections.OrderedDict(zip(words, itertools.count())) m = types.MappingProxyType(d) - self.assertEqual(pprint.pformat(m), """\ + self.assertEqual(_pformat(m), """\ mappingproxy(OrderedDict([('the', 0), ('quick', 1), ('brown', 2), @@ -606,22 +611,22 @@ def test_dict_views(self): k = d.keys() v = d.values() i = d.items() - self.assertEqual(pprint.pformat(k, sort_dicts=True), + self.assertEqual(_pformat(k, sort_dicts=True), prefix + "_keys([%s])" % joiner.join(repr(key) for key in sorted(k))) - self.assertEqual(pprint.pformat(v, sort_dicts=True), + self.assertEqual(_pformat(v, sort_dicts=True), prefix + "_values([%s])" % joiner.join(repr(val) for val in sorted(v))) - self.assertEqual(pprint.pformat(i, sort_dicts=True), + self.assertEqual(_pformat(i, sort_dicts=True), prefix + "_items([%s])" % joiner.join(repr(item) for item in sorted(i))) - self.assertEqual(pprint.pformat(k, sort_dicts=False), + self.assertEqual(_pformat(k, sort_dicts=False), prefix + "_keys([%s])" % joiner.join(repr(key) for key in k)) - self.assertEqual(pprint.pformat(v, sort_dicts=False), + self.assertEqual(_pformat(v, sort_dicts=False), prefix + "_values([%s])" % joiner.join(repr(val) for val in v)) - self.assertEqual(pprint.pformat(i, sort_dicts=False), + self.assertEqual(_pformat(i, sort_dicts=False), prefix + "_items([%s])" % joiner.join(repr(item) for item in i)) @@ -641,55 +646,55 @@ class MV(MappingView): pass s = sorted(i) joined_items = "({%s})" % joiner.join(["%r: %r" % (k, v) for (k, v) in i]) sorted_items = "({%s})" % joiner.join(["%r: %r" % (k, v) for (k, v) in s]) - self.assertEqual(pprint.pformat(KeysView(d), sort_dicts=True), + self.assertEqual(_pformat(KeysView(d), sort_dicts=True), KeysView.__name__ + sorted_items) - self.assertEqual(pprint.pformat(ItemsView(d), sort_dicts=True), + self.assertEqual(_pformat(ItemsView(d), sort_dicts=True), ItemsView.__name__ + sorted_items) - self.assertEqual(pprint.pformat(MappingView(d), sort_dicts=True), + self.assertEqual(_pformat(MappingView(d), sort_dicts=True), MappingView.__name__ + sorted_items) - self.assertEqual(pprint.pformat(MV(d), sort_dicts=True), + self.assertEqual(_pformat(MV(d), sort_dicts=True), MV.__name__ + sorted_items) - self.assertEqual(pprint.pformat(ValuesView(d), sort_dicts=True), + self.assertEqual(_pformat(ValuesView(d), sort_dicts=True), ValuesView.__name__ + sorted_items) - self.assertEqual(pprint.pformat(KeysView(d), sort_dicts=False), + self.assertEqual(_pformat(KeysView(d), sort_dicts=False), KeysView.__name__ + joined_items) - self.assertEqual(pprint.pformat(ItemsView(d), sort_dicts=False), + self.assertEqual(_pformat(ItemsView(d), sort_dicts=False), ItemsView.__name__ + joined_items) - self.assertEqual(pprint.pformat(MappingView(d), sort_dicts=False), + self.assertEqual(_pformat(MappingView(d), sort_dicts=False), MappingView.__name__ + joined_items) - self.assertEqual(pprint.pformat(MV(d), sort_dicts=False), + self.assertEqual(_pformat(MV(d), sort_dicts=False), MV.__name__ + joined_items) - self.assertEqual(pprint.pformat(ValuesView(d), sort_dicts=False), + self.assertEqual(_pformat(ValuesView(d), sort_dicts=False), ValuesView.__name__ + joined_items) def test_nested_views(self): d = {1: MappingView({1: MappingView({1: MappingView({1: 2})})})} self.assertEqual(repr(d), "{1: MappingView({1: MappingView({1: MappingView({1: 2})})})}") - self.assertEqual(pprint.pformat(d), + self.assertEqual(_pformat(d), "{1: MappingView({1: MappingView({1: MappingView({1: 2})})})}") - self.assertEqual(pprint.pformat(d, depth=2), + self.assertEqual(_pformat(d, depth=2), "{1: MappingView({1: {...}})}") d = {} d1 = {1: d.values()} d2 = {1: d1.values()} d3 = {1: d2.values()} - self.assertEqual(pprint.pformat(d3), + self.assertEqual(_pformat(d3), "{1: dict_values([dict_values([dict_values([])])])}") - self.assertEqual(pprint.pformat(d3, depth=2), + self.assertEqual(_pformat(d3, depth=2), "{1: dict_values([{...}])}") def test_unorderable_items_views(self): """Check that views with unorderable items have stable sorting.""" d = dict((((3+1j), 3), ((1+1j), (1+0j)), (1j, 0j), (500, None), (499, None))) iv = ItemsView(d) - self.assertEqual(pprint.pformat(iv), - pprint.pformat(iv)) - self.assertTrue(pprint.pformat(iv).endswith(", 499: None, 500: None})"), - pprint.pformat(iv)) - self.assertEqual(pprint.pformat(d.items()), # Won't be equal unless _safe_tuple - pprint.pformat(d.items())) # is used in _safe_repr - self.assertTrue(pprint.pformat(d.items()).endswith(", (499, None), (500, None)])")) + self.assertEqual(_pformat(iv), + _pformat(iv)) + self.assertTrue(_pformat(iv).endswith(", 499: None, 500: None})"), + _pformat(iv)) + self.assertEqual(_pformat(d.items()), # Won't be equal unless _safe_tuple + _pformat(d.items())) # is used in _safe_repr + self.assertTrue(_pformat(d.items()).endswith(", (499, None), (500, None)])")) def test_mapping_view_subclass_no_mapping(self): class BMV(MappingView): @@ -698,7 +703,7 @@ def __init__(self, d): self.mapping = self._mapping del self._mapping - self.assertRaises(AttributeError, pprint.pformat, BMV({})) + self.assertRaises(AttributeError, _pformat, BMV({})) def test_mapping_subclass_repr(self): """Test that mapping ABC views use their ._mapping's __repr__.""" @@ -722,10 +727,10 @@ def __repr__(self): self.assertEqual(repr(m), "MyMapping(['test', 1])") short_view_repr = "%s(MyMapping(['test', 1]))" self.assertEqual(repr(m.keys()), short_view_repr % "KeysView") - self.assertEqual(pprint.pformat(m.items()), short_view_repr % "ItemsView") - self.assertEqual(pprint.pformat(m.keys()), short_view_repr % "KeysView") - self.assertEqual(pprint.pformat(MappingView(m)), short_view_repr % "MappingView") - self.assertEqual(pprint.pformat(m.values()), short_view_repr % "ValuesView") + self.assertEqual(_pformat(m.items()), short_view_repr % "ItemsView") + self.assertEqual(_pformat(m.keys()), short_view_repr % "KeysView") + self.assertEqual(_pformat(MappingView(m)), short_view_repr % "MappingView") + self.assertEqual(_pformat(m.values()), short_view_repr % "ValuesView") alpha = "abcdefghijklmnopqrstuvwxyz" m = MyMapping(alpha) @@ -733,19 +738,19 @@ def __repr__(self): long_view_repr = "%%s(MyMapping([%s]))" % alpha_repr self.assertEqual(repr(m), "MyMapping([%s])" % alpha_repr) self.assertEqual(repr(m.keys()), long_view_repr % "KeysView") - self.assertEqual(pprint.pformat(m.items()), long_view_repr % "ItemsView") - self.assertEqual(pprint.pformat(m.keys()), long_view_repr % "KeysView") - self.assertEqual(pprint.pformat(MappingView(m)), long_view_repr % "MappingView") - self.assertEqual(pprint.pformat(m.values()), long_view_repr % "ValuesView") + self.assertEqual(_pformat(m.items()), long_view_repr % "ItemsView") + self.assertEqual(_pformat(m.keys()), long_view_repr % "KeysView") + self.assertEqual(_pformat(MappingView(m)), long_view_repr % "MappingView") + self.assertEqual(_pformat(m.values()), long_view_repr % "ValuesView") def test_empty_simple_namespace(self): ns = types.SimpleNamespace() - formatted = pprint.pformat(ns) + formatted = _pformat(ns) self.assertEqual(formatted, "namespace()") def test_small_simple_namespace(self): ns = types.SimpleNamespace(a=1, b=2) - formatted = pprint.pformat(ns) + formatted = _pformat(ns) self.assertEqual(formatted, "namespace(a=1, b=2)") def test_simple_namespace(self): @@ -760,7 +765,7 @@ def test_simple_namespace(self): lazy=7, dog=8, ) - formatted = pprint.pformat(ns, width=60, indent=4) + formatted = pprint.pformat(ns, width=60, indent=4, expand=False) self.assertEqual(formatted, """\ namespace(the=0, quick=1, @@ -785,7 +790,7 @@ class AdvancedNamespace(types.SimpleNamespace): pass lazy=7, dog=8, ) - formatted = pprint.pformat(ns, width=60) + formatted = _pformat(ns, width=60) self.assertEqual(formatted, """\ AdvancedNamespace(the=0, quick=1, @@ -799,17 +804,17 @@ class AdvancedNamespace(types.SimpleNamespace): pass def test_empty_dataclass(self): dc = dataclasses.make_dataclass("MyDataclass", ())() - formatted = pprint.pformat(dc) + formatted = _pformat(dc) self.assertEqual(formatted, "MyDataclass()") def test_small_dataclass(self): dc = dataclass1("text", 123) - formatted = pprint.pformat(dc) + formatted = _pformat(dc) self.assertEqual(formatted, "dataclass1(field1='text', field2=123, field3=False)") def test_larger_dataclass(self): dc = dataclass1("some fairly long text", int(1e10), True) - formatted = pprint.pformat([dc, dc], width=60, indent=4) + formatted = pprint.pformat([dc, dc], width=60, indent=4, expand=False) self.assertEqual(formatted, """\ [ dataclass1(field1='some fairly long text', field2=10000000000, @@ -820,12 +825,12 @@ def test_larger_dataclass(self): def test_dataclass_with_repr(self): dc = dataclass2() - formatted = pprint.pformat(dc, width=20) + formatted = _pformat(dc, width=20) self.assertEqual(formatted, "custom repr that doesn't fit within pprint width") def test_dataclass_no_repr(self): dc = dataclass3() - formatted = pprint.pformat(dc, width=10) + formatted = _pformat(dc, width=10) self.assertRegex( formatted, fr"<{re.escape(__name__)}.dataclass3 object at \w+>", @@ -834,7 +839,7 @@ def test_dataclass_no_repr(self): def test_recursive_dataclass(self): dc = dataclass4(None) dc.a = dc - formatted = pprint.pformat(dc, width=10) + formatted = _pformat(dc, width=10) self.assertEqual(formatted, """\ dataclass4(a=..., b=1)""") @@ -844,7 +849,7 @@ def test_cyclic_dataclass(self): dc6 = dataclass6(None) dc5.a = dc6 dc6.c = dc5 - formatted = pprint.pformat(dc5, width=10) + formatted = _pformat(dc5, width=10) self.assertEqual(formatted, """\ dataclass5(a=dataclass6(c=..., d=1), @@ -858,7 +863,7 @@ def test_subclassing(self): {'names with spaces': 'should be presented using repr()', others.should.not.be: like.this}""" - dotted_printer = DottedPrettyPrinter() + dotted_printer = DottedPrettyPrinter(indent=1, expand=False) self.assertEqual(dotted_printer.pformat(o), exp) # length(repr(obj)) < width @@ -870,9 +875,9 @@ def test_subclassing(self): self.assertEqual(dotted_printer.pformat(o2), exp2) def test_set_reprs(self): - self.assertEqual(pprint.pformat(set()), 'set()') - self.assertEqual(pprint.pformat(set(range(3))), '{0, 1, 2}') - self.assertEqual(pprint.pformat(set(range(7)), width=20), '''\ + self.assertEqual(_pformat(set()), 'set()') + self.assertEqual(_pformat(set(range(3))), '{0, 1, 2}') + self.assertEqual(_pformat(set(range(7)), width=20), '''\ {0, 1, 2, @@ -880,7 +885,7 @@ def test_set_reprs(self): 4, 5, 6}''') - self.assertEqual(pprint.pformat(set2(range(7)), width=20), '''\ + self.assertEqual(_pformat(set2(range(7)), width=20), '''\ set2({0, 1, 2, @@ -888,13 +893,13 @@ def test_set_reprs(self): 4, 5, 6})''') - self.assertEqual(pprint.pformat(set3(range(7)), width=20), + self.assertEqual(_pformat(set3(range(7)), width=20), 'set3({0, 1, 2, 3, 4, 5, 6})') - self.assertEqual(pprint.pformat(frozenset()), 'frozenset()') - self.assertEqual(pprint.pformat(frozenset(range(3))), + self.assertEqual(_pformat(frozenset()), 'frozenset()') + self.assertEqual(_pformat(frozenset(range(3))), 'frozenset({0, 1, 2})') - self.assertEqual(pprint.pformat(frozenset(range(7)), width=20), '''\ + self.assertEqual(_pformat(frozenset(range(7)), width=20), '''\ frozenset({0, 1, 2, @@ -902,7 +907,7 @@ def test_set_reprs(self): 4, 5, 6})''') - self.assertEqual(pprint.pformat(frozenset2(range(7)), width=20), '''\ + self.assertEqual(_pformat(frozenset2(range(7)), width=20), '''\ frozenset2({0, 1, 2, @@ -910,7 +915,7 @@ def test_set_reprs(self): 4, 5, 6})''') - self.assertEqual(pprint.pformat(frozenset3(range(7)), width=20), + self.assertEqual(_pformat(frozenset3(range(7)), width=20), 'frozenset3({0, 1, 2, 3, 4, 5, 6})') def test_set_of_sets_reprs(self): @@ -942,21 +947,21 @@ def test_set_of_sets_reprs(self): fs0 = frozenset() fs1 = frozenset(('abc', 'xyz')) data = frozenset((fs0, fs1)) - self.assertEqual(pprint.pformat(data), + self.assertEqual(_pformat(data), 'frozenset({%r, %r})' % (fs0, fs1)) - self.assertEqual(pprint.pformat(data), repr(data)) + self.assertEqual(_pformat(data), repr(data)) fs2 = frozenset(('one', 'two')) data = {fs2: frozenset((fs0, fs1))} - self.assertEqual(pprint.pformat(data), + self.assertEqual(_pformat(data), "{%r: frozenset({%r, %r})}" % (fs2, fs0, fs1)) - self.assertEqual(pprint.pformat(data), repr(data)) + self.assertEqual(_pformat(data), repr(data)) # Single-line, unordered: fs1 = frozenset(("xyz", "qwerty")) fs2 = frozenset(("abcd", "spam")) fs = frozenset((fs1, fs2)) - self.assertEqual(pprint.pformat(fs), repr(fs)) + self.assertEqual(_pformat(fs), repr(fs)) # Multiline, unordered: def check(res, invariants): @@ -966,7 +971,7 @@ def check(res, invariants): fs1 = frozenset(('regular string', 'other string')) fs2 = frozenset(('third string', 'one more string')) check( - pprint.pformat(frozenset((fs1, fs2))), + _pformat(frozenset((fs1, fs2))), [ """ frozenset({%r, @@ -981,7 +986,7 @@ def check(res, invariants): # Everything is multiline, unordered: check( - pprint.pformat( + _pformat( frozenset(( frozenset(( "xyz very-very long string", @@ -1028,16 +1033,16 @@ def test_depth(self): nested_tuple = (1, (2, (3, (4, (5, 6))))) nested_dict = {1: {2: {3: {4: {5: {6: 6}}}}}} nested_list = [1, [2, [3, [4, [5, [6, []]]]]]] - self.assertEqual(pprint.pformat(nested_tuple), repr(nested_tuple)) - self.assertEqual(pprint.pformat(nested_dict), repr(nested_dict)) - self.assertEqual(pprint.pformat(nested_list), repr(nested_list)) + self.assertEqual(_pformat(nested_tuple), repr(nested_tuple)) + self.assertEqual(_pformat(nested_dict), repr(nested_dict)) + self.assertEqual(_pformat(nested_list), repr(nested_list)) lv1_tuple = '(1, (...))' lv1_dict = '{1: {...}}' lv1_list = '[1, [...]]' - self.assertEqual(pprint.pformat(nested_tuple, depth=1), lv1_tuple) - self.assertEqual(pprint.pformat(nested_dict, depth=1), lv1_dict) - self.assertEqual(pprint.pformat(nested_list, depth=1), lv1_list) + self.assertEqual(_pformat(nested_tuple, depth=1), lv1_tuple) + self.assertEqual(_pformat(nested_dict, depth=1), lv1_dict) + self.assertEqual(_pformat(nested_list, depth=1), lv1_list) def test_sort_unorderable_values(self): # Issue 3976: sorted pprints fail for unorderable values. @@ -1047,24 +1052,24 @@ def test_sort_unorderable_values(self): skeys = sorted(keys, key=id) clean = lambda s: s.replace(' ', '').replace('\n','') - self.assertEqual(clean(pprint.pformat(set(keys))), + self.assertEqual(clean(_pformat(set(keys))), '{' + ','.join(map(repr, skeys)) + '}') - self.assertEqual(clean(pprint.pformat(frozenset(keys))), + self.assertEqual(clean(_pformat(frozenset(keys))), 'frozenset({' + ','.join(map(repr, skeys)) + '})') - self.assertEqual(clean(pprint.pformat(dict.fromkeys(keys))), + self.assertEqual(clean(_pformat(dict.fromkeys(keys))), '{' + ','.join('%r:None' % k for k in skeys) + '}') - self.assertEqual(clean(pprint.pformat(dict.fromkeys(keys).keys())), + self.assertEqual(clean(_pformat(dict.fromkeys(keys).keys())), 'dict_keys([' + ','.join('%r' % k for k in skeys) + '])') - self.assertEqual(clean(pprint.pformat(dict.fromkeys(keys).items())), + self.assertEqual(clean(_pformat(dict.fromkeys(keys).items())), 'dict_items([' + ','.join('(%r,None)' % k for k in skeys) + '])') # Issue 10017: TypeError on user-defined types as dict keys. - self.assertEqual(pprint.pformat({Unorderable: 0, 1: 0}), + self.assertEqual(_pformat({Unorderable: 0, 1: 0}), '{1: 0, ' + repr(Unorderable) +': 0}') # Issue 14998: TypeError on tuples with NoneTypes as dict keys. keys = [(1,), (None,)] - self.assertEqual(pprint.pformat(dict.fromkeys(keys, 0)), + self.assertEqual(_pformat(dict.fromkeys(keys, 0)), '{%r: 0, %r: 0}' % tuple(sorted(keys, key=id))) def test_sort_orderable_and_unorderable_values(self): @@ -1077,24 +1082,24 @@ def test_sort_orderable_and_unorderable_values(self): self.assertEqual(sorted([b, a]), [a, b]) self.assertEqual(sorted([a, b]), [a, b]) # set - self.assertEqual(pprint.pformat(set([b, a]), width=1), + self.assertEqual(_pformat(set([b, a]), width=1), '{%r,\n %r}' % (a, b)) - self.assertEqual(pprint.pformat(set([a, b]), width=1), + self.assertEqual(_pformat(set([a, b]), width=1), '{%r,\n %r}' % (a, b)) # dict - self.assertEqual(pprint.pformat(dict.fromkeys([b, a]), width=1), + self.assertEqual(_pformat(dict.fromkeys([b, a]), width=1), '{%r: None,\n %r: None}' % (a, b)) - self.assertEqual(pprint.pformat(dict.fromkeys([a, b]), width=1), + self.assertEqual(_pformat(dict.fromkeys([a, b]), width=1), '{%r: None,\n %r: None}' % (a, b)) 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=19), """\ + self.assertEqual(_pformat(fox, width=19), """\ ('the quick brown ' 'fox jumped over ' 'a lazy dog')""") - self.assertEqual(pprint.pformat({'a': 1, 'b': fox, 'c': 2}, + self.assertEqual(_pformat({'a': 1, 'b': fox, 'c': 2}, width=25), """\ {'a': 1, 'b': 'the quick brown ' @@ -1107,28 +1112,28 @@ def test_str_wrap(self): # - 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=68), repr(special)) - self.assertEqual(pprint.pformat(special, width=31), """\ + self.assertEqual(_pformat(special, width=68), repr(special)) + self.assertEqual(_pformat(special, width=31), """\ ('Portons dix bons "whiskys"\\n' "à l'avocat goujat\\t qui " 'fumait au zoo')""") - self.assertEqual(pprint.pformat(special, width=20), """\ + self.assertEqual(_pformat(special, width=20), """\ ('Portons dix bons ' '"whiskys"\\n' "à l'avocat " 'goujat\\t qui ' 'fumait au zoo')""") - self.assertEqual(pprint.pformat([[[[[special]]]]], width=35), """\ + self.assertEqual(_pformat([[[[[special]]]]], width=35), """\ [[[[['Portons dix bons "whiskys"\\n' "à l'avocat goujat\\t qui " 'fumait au zoo']]]]]""") - self.assertEqual(pprint.pformat([[[[[special]]]]], width=25), """\ + self.assertEqual(_pformat([[[[[special]]]]], width=25), """\ [[[[['Portons dix bons ' '"whiskys"\\n' "à l'avocat " 'goujat\\t qui ' 'fumait au zoo']]]]]""") - self.assertEqual(pprint.pformat([[[[[special]]]]], width=23), """\ + self.assertEqual(_pformat([[[[[special]]]]], width=23), """\ [[[[['Portons dix ' 'bons "whiskys"\\n' "à l'avocat " @@ -1137,14 +1142,14 @@ def test_str_wrap(self): 'zoo']]]]]""") # An unwrappable string is formatted as its repr unwrappable = "x" * 100 - self.assertEqual(pprint.pformat(unwrappable, width=80), repr(unwrappable)) - self.assertEqual(pprint.pformat(''), "''") + self.assertEqual(_pformat(unwrappable, width=80), repr(unwrappable)) + self.assertEqual(_pformat(''), "''") # Check that the pprint is a usable repr special *= 10 for width in range(3, 40): - formatted = pprint.pformat(special, width=width) + formatted = _pformat(special, width=width) self.assertEqual(eval(formatted), special) - formatted = pprint.pformat([special] * 2, width=width) + formatted = _pformat([special] * 2, width=width) self.assertEqual(eval(formatted), [special] * 2) def test_compact(self): @@ -1157,7 +1162,7 @@ def test_compact(self): 14, 15], [], [0], [0, 1], [0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4]]""" - self.assertEqual(pprint.pformat(o, width=47, compact=True), expected) + self.assertEqual(_pformat(o, width=47, compact=True), expected) def test_compact_width(self): levels = 20 @@ -1166,117 +1171,117 @@ def test_compact_width(self): 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() + lines = _pformat(o, width=w, compact=True).splitlines() maxwidth = max(map(len, lines)) self.assertLessEqual(maxwidth, w) self.assertGreater(maxwidth, w - 3) def test_bytes_wrap(self): - self.assertEqual(pprint.pformat(b'', width=1), "b''") - self.assertEqual(pprint.pformat(b'abcd', width=1), "b'abcd'") + self.assertEqual(_pformat(b'', width=1), "b''") + self.assertEqual(_pformat(b'abcd', width=1), "b'abcd'") letters = b'abcdefghijklmnopqrstuvwxyz' - self.assertEqual(pprint.pformat(letters, width=29), repr(letters)) - self.assertEqual(pprint.pformat(letters, width=19), """\ + self.assertEqual(_pformat(letters, width=29), repr(letters)) + self.assertEqual(_pformat(letters, width=19), """\ (b'abcdefghijkl' b'mnopqrstuvwxyz')""") - self.assertEqual(pprint.pformat(letters, width=18), """\ + self.assertEqual(_pformat(letters, width=18), """\ (b'abcdefghijkl' b'mnopqrstuvwx' b'yz')""") - self.assertEqual(pprint.pformat(letters, width=16), """\ + self.assertEqual(_pformat(letters, width=16), """\ (b'abcdefghijkl' b'mnopqrstuvwx' b'yz')""") special = bytes(range(16)) - self.assertEqual(pprint.pformat(special, width=61), repr(special)) - self.assertEqual(pprint.pformat(special, width=48), """\ + self.assertEqual(_pformat(special, width=61), repr(special)) + self.assertEqual(_pformat(special, width=48), """\ (b'\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\n\\x0b' b'\\x0c\\r\\x0e\\x0f')""") - self.assertEqual(pprint.pformat(special, width=32), """\ + self.assertEqual(_pformat(special, width=32), """\ (b'\\x00\\x01\\x02\\x03' b'\\x04\\x05\\x06\\x07\\x08\\t\\n\\x0b' b'\\x0c\\r\\x0e\\x0f')""") - self.assertEqual(pprint.pformat(special, width=1), """\ + self.assertEqual(_pformat(special, width=1), """\ (b'\\x00\\x01\\x02\\x03' b'\\x04\\x05\\x06\\x07' b'\\x08\\t\\n\\x0b' b'\\x0c\\r\\x0e\\x0f')""") - self.assertEqual(pprint.pformat({'a': 1, 'b': letters, 'c': 2}, + self.assertEqual(_pformat({'a': 1, 'b': letters, 'c': 2}, width=21), """\ {'a': 1, 'b': b'abcdefghijkl' b'mnopqrstuvwx' b'yz', 'c': 2}""") - self.assertEqual(pprint.pformat({'a': 1, 'b': letters, 'c': 2}, + self.assertEqual(_pformat({'a': 1, 'b': letters, 'c': 2}, width=20), """\ {'a': 1, 'b': b'abcdefgh' b'ijklmnop' b'qrstuvwxyz', 'c': 2}""") - self.assertEqual(pprint.pformat([[[[[[letters]]]]]], width=25), """\ + self.assertEqual(_pformat([[[[[[letters]]]]]], width=25), """\ [[[[[[b'abcdefghijklmnop' b'qrstuvwxyz']]]]]]""") - self.assertEqual(pprint.pformat([[[[[[special]]]]]], width=41), """\ + self.assertEqual(_pformat([[[[[[special]]]]]], width=41), """\ [[[[[[b'\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07' b'\\x08\\t\\n\\x0b\\x0c\\r\\x0e\\x0f']]]]]]""") # Check that the pprint is a usable repr for width in range(1, 64): - formatted = pprint.pformat(special, width=width) + formatted = _pformat(special, width=width) self.assertEqual(eval(formatted), special) - formatted = pprint.pformat([special] * 2, width=width) + formatted = _pformat([special] * 2, width=width) self.assertEqual(eval(formatted), [special] * 2) def test_bytearray_wrap(self): - self.assertEqual(pprint.pformat(bytearray(), width=1), "bytearray(b'')") + self.assertEqual(_pformat(bytearray(), width=1), "bytearray(b'')") letters = bytearray(b'abcdefghijklmnopqrstuvwxyz') - self.assertEqual(pprint.pformat(letters, width=40), repr(letters)) - self.assertEqual(pprint.pformat(letters, width=28), """\ + self.assertEqual(_pformat(letters, width=40), repr(letters)) + self.assertEqual(_pformat(letters, width=28), """\ bytearray(b'abcdefghijkl' b'mnopqrstuvwxyz')""") - self.assertEqual(pprint.pformat(letters, width=27), """\ + self.assertEqual(_pformat(letters, width=27), """\ bytearray(b'abcdefghijkl' b'mnopqrstuvwx' b'yz')""") - self.assertEqual(pprint.pformat(letters, width=25), """\ + self.assertEqual(_pformat(letters, width=25), """\ bytearray(b'abcdefghijkl' b'mnopqrstuvwx' b'yz')""") special = bytearray(range(16)) - self.assertEqual(pprint.pformat(special, width=72), repr(special)) - self.assertEqual(pprint.pformat(special, width=57), """\ + self.assertEqual(_pformat(special, width=72), repr(special)) + self.assertEqual(_pformat(special, width=57), """\ bytearray(b'\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\n\\x0b' b'\\x0c\\r\\x0e\\x0f')""") - self.assertEqual(pprint.pformat(special, width=41), """\ + self.assertEqual(_pformat(special, width=41), """\ bytearray(b'\\x00\\x01\\x02\\x03' b'\\x04\\x05\\x06\\x07\\x08\\t\\n\\x0b' b'\\x0c\\r\\x0e\\x0f')""") - self.assertEqual(pprint.pformat(special, width=1), """\ + self.assertEqual(_pformat(special, width=1), """\ bytearray(b'\\x00\\x01\\x02\\x03' b'\\x04\\x05\\x06\\x07' b'\\x08\\t\\n\\x0b' b'\\x0c\\r\\x0e\\x0f')""") - self.assertEqual(pprint.pformat({'a': 1, 'b': letters, 'c': 2}, + self.assertEqual(_pformat({'a': 1, 'b': letters, 'c': 2}, width=31), """\ {'a': 1, 'b': bytearray(b'abcdefghijkl' b'mnopqrstuvwx' b'yz'), 'c': 2}""") - self.assertEqual(pprint.pformat([[[[[letters]]]]], width=37), """\ + self.assertEqual(_pformat([[[[[letters]]]]], width=37), """\ [[[[[bytearray(b'abcdefghijklmnop' b'qrstuvwxyz')]]]]]""") - self.assertEqual(pprint.pformat([[[[[special]]]]], width=50), """\ + self.assertEqual(_pformat([[[[[special]]]]], width=50), """\ [[[[[bytearray(b'\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07' b'\\x08\\t\\n\\x0b\\x0c\\r\\x0e\\x0f')]]]]]""") def test_default_dict(self): d = collections.defaultdict(int) - self.assertEqual(pprint.pformat(d, width=1), "defaultdict(, {})") + self.assertEqual(_pformat(d, width=1), "defaultdict(, {})") words = 'the quick brown fox jumped over a lazy dog'.split() d = collections.defaultdict(int, zip(words, itertools.count())) - self.assertEqual(pprint.pformat(d), + self.assertEqual(_pformat(d), """\ defaultdict(, {'a': 6, @@ -1291,15 +1296,15 @@ def test_default_dict(self): def test_counter(self): d = collections.Counter() - self.assertEqual(pprint.pformat(d, width=1), "Counter()") + self.assertEqual(_pformat(d, width=1), "Counter()") d = collections.Counter('senselessness') - self.assertEqual(pprint.pformat(d, width=40), + self.assertEqual(_pformat(d, width=40), """\ Counter({'s': 6, 'e': 4, 'n': 2, 'l': 1})""") - self.assertEqual(pprint.pformat(d, indent=2, width=1), + self.assertEqual(_pformat(d, indent=2, width=1), """\ Counter({ 's': 6, 'e': 4, @@ -1308,11 +1313,11 @@ def test_counter(self): def test_chainmap(self): d = collections.ChainMap() - self.assertEqual(pprint.pformat(d, width=1), "ChainMap({})") + self.assertEqual(_pformat(d, width=1), "ChainMap({})") words = 'the quick brown fox jumped over a lazy dog'.split() items = list(zip(words, itertools.count())) d = collections.ChainMap(dict(items)) - self.assertEqual(pprint.pformat(d), + self.assertEqual(_pformat(d), """\ ChainMap({'a': 6, 'brown': 2, @@ -1324,7 +1329,7 @@ def test_chainmap(self): 'quick': 1, 'the': 0})""") d = collections.ChainMap(dict(items), collections.OrderedDict(items)) - self.assertEqual(pprint.pformat(d), + self.assertEqual(_pformat(d), """\ ChainMap({'a': 6, 'brown': 2, @@ -1344,7 +1349,7 @@ def test_chainmap(self): ('a', 6), ('lazy', 7), ('dog', 8)]))""") - self.assertEqual(pprint.pformat(d.keys()), + self.assertEqual(_pformat(d.keys()), """\ KeysView(ChainMap({'a': 6, 'brown': 2, @@ -1364,7 +1369,7 @@ def test_chainmap(self): ('a', 6), ('lazy', 7), ('dog', 8)])))""") - self.assertEqual(pprint.pformat(d.items()), + self.assertEqual(_pformat(d.items()), """\ ItemsView(ChainMap({'a': 6, 'brown': 2, @@ -1384,7 +1389,7 @@ def test_chainmap(self): ('a', 6), ('lazy', 7), ('dog', 8)])))""") - self.assertEqual(pprint.pformat(d.values()), + self.assertEqual(_pformat(d.values()), """\ ValuesView(ChainMap({'a': 6, 'brown': 2, @@ -1407,12 +1412,12 @@ def test_chainmap(self): def test_deque(self): d = collections.deque() - self.assertEqual(pprint.pformat(d, width=1), "deque([])") + self.assertEqual(_pformat(d, width=1), "deque([])") d = collections.deque(maxlen=7) - self.assertEqual(pprint.pformat(d, width=1), "deque([], maxlen=7)") + self.assertEqual(_pformat(d, width=1), "deque([], maxlen=7)") words = 'the quick brown fox jumped over a lazy dog'.split() d = collections.deque(zip(words, itertools.count())) - self.assertEqual(pprint.pformat(d), + self.assertEqual(_pformat(d), """\ deque([('the', 0), ('quick', 1), @@ -1424,7 +1429,7 @@ def test_deque(self): ('lazy', 7), ('dog', 8)])""") d = collections.deque(zip(words, itertools.count()), maxlen=7) - self.assertEqual(pprint.pformat(d), + self.assertEqual(_pformat(d), """\ deque([('brown', 2), ('fox', 3), @@ -1437,10 +1442,10 @@ def test_deque(self): def test_user_dict(self): d = collections.UserDict() - self.assertEqual(pprint.pformat(d, width=1), "{}") + self.assertEqual(_pformat(d, width=1), "{}") words = 'the quick brown fox jumped over a lazy dog'.split() d = collections.UserDict(zip(words, itertools.count())) - self.assertEqual(pprint.pformat(d), + self.assertEqual(_pformat(d), """\ {'a': 6, 'brown': 2, @@ -1451,7 +1456,7 @@ def test_user_dict(self): 'over': 5, 'quick': 1, 'the': 0}""") - self.assertEqual(pprint.pformat(d.keys()), """\ + self.assertEqual(_pformat(d.keys()), """\ KeysView({'a': 6, 'brown': 2, 'dog': 8, @@ -1461,7 +1466,7 @@ def test_user_dict(self): 'over': 5, 'quick': 1, 'the': 0})""") - self.assertEqual(pprint.pformat(d.items()), """\ + self.assertEqual(_pformat(d.items()), """\ ItemsView({'a': 6, 'brown': 2, 'dog': 8, @@ -1471,7 +1476,7 @@ def test_user_dict(self): 'over': 5, 'quick': 1, 'the': 0})""") - self.assertEqual(pprint.pformat(d.values()), """\ + self.assertEqual(_pformat(d.values()), """\ ValuesView({'a': 6, 'brown': 2, 'dog': 8, @@ -1484,10 +1489,10 @@ def test_user_dict(self): def test_user_list(self): d = collections.UserList() - self.assertEqual(pprint.pformat(d, width=1), "[]") + self.assertEqual(_pformat(d, width=1), "[]") words = 'the quick brown fox jumped over a lazy dog'.split() d = collections.UserList(zip(words, itertools.count())) - self.assertEqual(pprint.pformat(d), + self.assertEqual(_pformat(d), """\ [('the', 0), ('quick', 1), @@ -1501,14 +1506,14 @@ def test_user_list(self): def test_user_string(self): d = collections.UserString('') - self.assertEqual(pprint.pformat(d, width=1), "''") + self.assertEqual(_pformat(d, width=1), "''") d = collections.UserString('the quick brown fox jumped over a lazy dog') - self.assertEqual(pprint.pformat(d, width=20), + self.assertEqual(_pformat(d, width=20), """\ ('the quick brown ' 'fox jumped over ' 'a lazy dog')""") - self.assertEqual(pprint.pformat({1: d}, width=20), + self.assertEqual(_pformat({1: d}, width=20), """\ {1: 'the quick ' 'brown fox ' @@ -1517,22 +1522,22 @@ def test_user_string(self): def test_template(self): d = t"" - self.assertEqual(pprint.pformat(d), + self.assertEqual(_pformat(d), "Template(strings=('',), interpolations=())") - self.assertEqual(pprint.pformat(d), repr(d)) - self.assertEqual(pprint.pformat(d, width=1), + self.assertEqual(_pformat(d), repr(d)) + self.assertEqual(_pformat(d, width=1), """\ Template(strings=('',), interpolations=())""") name = "World" d = t"Hello {name}" - self.assertEqual(pprint.pformat(d), + self.assertEqual(_pformat(d), """\ Template(strings=('Hello ', ''), interpolations=(Interpolation('World', 'name', None, ''),))""") ver = {3.13: False, 3.14: True} d = t"Hello { {"name": "Python", "version": ver}!s:z}!" - self.assertEqual(pprint.pformat(d, width=1), + self.assertEqual(_pformat(d, width=1), """\ Template(strings=('Hello ', '!'), diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index ed0868e0017fce..33af75ccf5682b 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -1017,15 +1017,19 @@ def test_windows_feature_macros(self): 'PyOS_CheckStack', ) -EXPECTED_FEATURE_MACROS = set(['HAVE_FORK', - 'MS_WINDOWS', - 'PY_HAVE_THREAD_NATIVE_ID', - 'Py_REF_DEBUG', - 'Py_TRACE_REFS', - 'USE_STACKCHECK']) -WINDOWS_FEATURE_MACROS = {'HAVE_FORK': False, - 'MS_WINDOWS': True, - 'PY_HAVE_THREAD_NATIVE_ID': True, - 'Py_REF_DEBUG': 'maybe', - 'Py_TRACE_REFS': 'maybe', - 'USE_STACKCHECK': 'maybe'} +EXPECTED_FEATURE_MACROS = set([ + 'HAVE_FORK', + 'MS_WINDOWS', + 'PY_HAVE_THREAD_NATIVE_ID', + 'Py_REF_DEBUG', + 'Py_TRACE_REFS', + 'USE_STACKCHECK', +]) +WINDOWS_FEATURE_MACROS = { + 'HAVE_FORK': False, + 'MS_WINDOWS': True, + 'PY_HAVE_THREAD_NATIVE_ID': True, + 'Py_REF_DEBUG': 'maybe', + 'Py_TRACE_REFS': 'maybe', + 'USE_STACKCHECK': 'maybe', +} diff --git a/Lib/test/test_unittest/testmock/testhelpers.py b/Lib/test/test_unittest/testmock/testhelpers.py index 0e82c723ec3eaa..f8643552011f4e 100644 --- a/Lib/test/test_unittest/testmock/testhelpers.py +++ b/Lib/test/test_unittest/testmock/testhelpers.py @@ -1162,9 +1162,7 @@ def test_call_list_str(self): mock.foo.bar().baz('fish', cat='dog') expected = ( - "[call(1, 2),\n" - " call.foo(a=3),\n" - " call.foo.bar(),\n" + "[call(1, 2), call.foo(a=3), call.foo.bar()," " call.foo.bar().baz('fish', cat='dog')]" ) self.assertEqual(str(mock.mock_calls), expected) diff --git a/Misc/NEWS.d/next/Library/2026-04-30-18-56-23.gh-issue-149189.mszW10.rst b/Misc/NEWS.d/next/Library/2026-04-30-18-56-23.gh-issue-149189.mszW10.rst new file mode 100644 index 00000000000000..47511146237f7b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-30-18-56-23.gh-issue-149189.mszW10.rst @@ -0,0 +1,2 @@ +:mod:`pprint` now uses modern defaults: ``indent=4, width=88, expand=True``. +Patch by Hugo van Kemenade. From bc0d9dae80b84f54c2b501aea5b20ec53490cd21 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 30 Apr 2026 23:46:58 +0300 Subject: [PATCH 2/8] Update test results --- Doc/library/difflib.rst | 22 ++++++++++++---------- Doc/library/pprint.rst | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Doc/library/difflib.rst b/Doc/library/difflib.rst index 8b812c173b5953..e5afa174413541 100644 --- a/Doc/library/difflib.rst +++ b/Doc/library/difflib.rst @@ -728,16 +728,18 @@ Finally, we compare the two: >>> from pprint import pprint >>> pprint(result) - [' 1. Beautiful is better than ugly.\n', - '- 2. Explicit is better than implicit.\n', - '- 3. Simple is better than complex.\n', - '+ 3. Simple is better than complex.\n', - '? ++\n', - '- 4. Complex is better than complicated.\n', - '? ^ ---- ^\n', - '+ 4. Complicated is better than complex.\n', - '? ++++ ^ ^\n', - '+ 5. Flat is better than nested.\n'] + [ + ' 1. Beautiful is better than ugly.\n', + '- 2. Explicit is better than implicit.\n', + '- 3. Simple is better than complex.\n', + '+ 3. Simple is better than complex.\n', + '? ++\n', + '- 4. Complex is better than complicated.\n', + '? ^ ---- ^\n', + '+ 4. Complicated is better than complex.\n', + '? ++++ ^ ^\n', + '+ 5. Flat is better than nested.\n', + ] As a single multi-line string it looks like this: diff --git a/Doc/library/pprint.rst b/Doc/library/pprint.rst index a7011e13b1a03b..b0bf406273d25f 100644 --- a/Doc/library/pprint.rst +++ b/Doc/library/pprint.rst @@ -91,7 +91,7 @@ Functions >>> import pprint >>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni'] >>> stuff.insert(0, stuff) - >>> pprint.pp(stuff) + >>> pprint.pp(stuff, width=100) [, 'spam', 'eggs', 'lumberjack', 'knights', 'ni'] .. versionadded:: 3.8 From 67c9eaa0e08d9d9ec1161a4b9ec99a6cc963f045 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 2 May 2026 16:56:43 +0300 Subject: [PATCH 3/8] Note old defaults --- Doc/whatsnew/3.15.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 1d023a90adc456..eb91bae31941fb 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1095,6 +1095,7 @@ pprint :gh:`112632`.) * :mod:`pprint` now uses modern defaults: ``indent=4, width=88, expand=True``. + Use ``indent=1, width=80, expand=False`` to retain old output. (Contributed by Hugo van Kemenade in :gh:`149189`.) * Add t-string support to :mod:`pprint`. From 4b6680b8a606a8b092154752c7cf9c8fa7edf55c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 2 May 2026 20:36:48 +0300 Subject: [PATCH 4/8] Update defaults in doctest --- Doc/library/pprint.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/library/pprint.rst b/Doc/library/pprint.rst index b0bf406273d25f..f7adb35571507b 100644 --- a/Doc/library/pprint.rst +++ b/Doc/library/pprint.rst @@ -166,7 +166,7 @@ PrettyPrinter objects >>> import pprint >>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni'] >>> stuff.insert(0, stuff[:]) - >>> pp = pprint.PrettyPrinter(indent=4) + >>> pp = pprint.PrettyPrinter() >>> pp.pprint(stuff) [ ['spam', 'eggs', 'lumberjack', 'knights', 'ni'], @@ -176,12 +176,12 @@ PrettyPrinter objects 'knights', 'ni', ] - >>> pp = pprint.PrettyPrinter(width=41, compact=True, expand=False) + >>> pp = pprint.PrettyPrinter(indent=1, width=41, compact=True, expand=False) >>> pp.pprint(stuff) - [ [ 'spam', 'eggs', 'lumberjack', - 'knights', 'ni'], - 'spam', 'eggs', 'lumberjack', - 'knights', 'ni'] + [['spam', 'eggs', 'lumberjack', + 'knights', 'ni'], + 'spam', 'eggs', 'lumberjack', 'knights', + 'ni'] >>> pp = pprint.PrettyPrinter(width=41, indent=3) >>> pp.pprint(stuff) [ From 47d0a5d55f69c05ed26d5e44d9607409b58431cb Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 2 May 2026 20:37:50 +0300 Subject: [PATCH 5/8] Update versionchanged --- Doc/library/pprint.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/library/pprint.rst b/Doc/library/pprint.rst index f7adb35571507b..bf4bdc1a188728 100644 --- a/Doc/library/pprint.rst +++ b/Doc/library/pprint.rst @@ -222,8 +222,7 @@ PrettyPrinter objects .. versionchanged:: next Changed default *indent* from 1 to 4, - default *width* from 80 to 88, - and default *expand* from ``False`` to ``True``. + and default *width* from 80 to 88. :class:`PrettyPrinter` instances have the following methods: From 3c5d8f40b450f2b797d8545769de993d59f8ff90 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 3 May 2026 14:00:33 +0300 Subject: [PATCH 6/8] Remove ValueError if compact=expand=True, compact takes precendence --- Doc/library/pprint.rst | 10 ++++++---- Lib/pprint.py | 8 +++----- Lib/test/test_pprint.py | 9 ++++++++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Doc/library/pprint.rst b/Doc/library/pprint.rst index bf4bdc1a188728..6bf25f8476e8ff 100644 --- a/Doc/library/pprint.rst +++ b/Doc/library/pprint.rst @@ -70,13 +70,13 @@ Functions each item of a sequence will be formatted on a separate line, otherwise as many items as will fit within the *width* will be formatted on each output line. - Incompatible with *expand*. + Takes precedence over *expand*. :param bool expand: - If ``True``, + If ``True`` (the default), opening parentheses and brackets will be followed by a newline and the following content will be indented by one level, similar to - pretty-printed JSON. Incompatible with *compact*. + pretty-printed JSON. Has no effect when *compact* is also ``True``. :param bool sort_dicts: If ``True``, dictionaries will be formatted with @@ -222,7 +222,9 @@ PrettyPrinter objects .. versionchanged:: next Changed default *indent* from 1 to 4, - and default *width* from 80 to 88. + default *width* from 80 to 88, + and default *expand* from ``False`` to ``True``. + *compact* takes precedence over *expand* when both are ``True``. :class:`PrettyPrinter` instances have the following methods: diff --git a/Lib/pprint.py b/Lib/pprint.py index a94a333a06d6cb..b8307ce3dc7b69 100644 --- a/Lib/pprint.py +++ b/Lib/pprint.py @@ -159,12 +159,12 @@ def __init__( compact If true, several items will be combined in one line. - Incompatible with expand mode. + Takes precedence over expand mode. expand If true, the output will be formatted similar to pretty-printed json.dumps() when ``indent`` is supplied. - Incompatible with compact mode. + Has no effect if compact mode is also enabled. sort_dicts If true, dict keys are sorted. @@ -181,8 +181,6 @@ def __init__( raise ValueError('depth must be > 0') if not width: raise ValueError('width must be != 0') - if compact and expand: - raise ValueError('compact and expand are incompatible') self._depth = depth self._indent_per_level = indent self._width = width @@ -191,7 +189,7 @@ def __init__( else: self._stream = _sys.stdout self._compact = bool(compact) - self._expand = bool(expand) + self._expand = bool(expand) and not self._compact self._sort_dicts = sort_dicts self._underscore_numbers = underscore_numbers diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py index 6a4e33d82f5b2e..f4a81963b79760 100644 --- a/Lib/test/test_pprint.py +++ b/Lib/test/test_pprint.py @@ -169,7 +169,6 @@ def test_init(self): self.assertRaises(ValueError, pprint.PrettyPrinter, depth=0) self.assertRaises(ValueError, pprint.PrettyPrinter, depth=-1) self.assertRaises(ValueError, pprint.PrettyPrinter, width=0) - self.assertRaises(ValueError, pprint.PrettyPrinter, compact=True, expand=True) def test_basic(self): # Verify .isrecursive() and .isreadable() w/o recursion @@ -1164,6 +1163,14 @@ def test_compact(self): [0, 1, 2, 3, 4]]""" self.assertEqual(_pformat(o, width=47, compact=True), expected) + def test_compact_without_explicit_expand(self): + # Passing compact=True alone should not require also passing + # expand=False, even though expand defaults to True. + self.assertEqual( + pprint.pformat([1, 2, 3, 4, 5], width=10, compact=True), + "[ 1, 2,\n 3, 4,\n 5]", + ) + def test_compact_width(self): levels = 20 number = 10 From cce0ee4641e5140c4806383aca57917b2a0b818c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 3 May 2026 15:47:02 +0300 Subject: [PATCH 7/8] Add note about compact=False --- Doc/library/pprint.rst | 11 ++++++++--- Lib/pprint.py | 7 ++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Doc/library/pprint.rst b/Doc/library/pprint.rst index 6bf25f8476e8ff..59eb7a059f35b7 100644 --- a/Doc/library/pprint.rst +++ b/Doc/library/pprint.rst @@ -66,10 +66,13 @@ Functions :param bool compact: Control the way long :term:`sequences ` are formatted. - If ``False`` (the default), - each item of a sequence will be formatted on a separate line, - otherwise as many items as will fit within the *width* + If ``True``, as many items as will fit within the *width* will be formatted on each output line. + If ``False`` (the default), each item of a sequence + will be formatted on a separate line, + *unless* *expand* mode is also active (which is now the default); + to opt out of *expand* mode, pass ``expand=False`` rather than + relying on ``compact=False``. Takes precedence over *expand*. :param bool expand: @@ -77,6 +80,8 @@ Functions opening parentheses and brackets will be followed by a newline and the following content will be indented by one level, similar to pretty-printed JSON. Has no effect when *compact* is also ``True``. + Pass ``expand=False`` to restore the pre-3.15 layout where each + item appears on its own line without the extra indentation. :param bool sort_dicts: If ``True``, dictionaries will be formatted with diff --git a/Lib/pprint.py b/Lib/pprint.py index b8307ce3dc7b69..8575a36e325967 100644 --- a/Lib/pprint.py +++ b/Lib/pprint.py @@ -159,11 +159,12 @@ def __init__( compact If true, several items will be combined in one line. - Takes precedence over expand mode. + Takes precedence over expand mode. The default (false) no longer + disables expand mode on its own; pass ``expand=False`` for that. expand - If true, the output will be formatted similar to - pretty-printed json.dumps() when ``indent`` is supplied. + If true (the default), the output will be formatted similar + to pretty-printed json.dumps() when ``indent`` is supplied. Has no effect if compact mode is also enabled. sort_dicts From cfb5a76c9277f60c4258d1fcff5a0144320da5a0 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 3 May 2026 22:18:08 +0300 Subject: [PATCH 8/8] Remove expand option, this is now the default --- Doc/library/pprint.rst | 102 +---- Doc/whatsnew/3.15.rst | 13 +- Lib/pprint.py | 139 +++---- Lib/test/test_pprint.py | 370 +++++++----------- ...-04-30-18-56-23.gh-issue-149189.mszW10.rst | 7 +- 5 files changed, 237 insertions(+), 394 deletions(-) diff --git a/Doc/library/pprint.rst b/Doc/library/pprint.rst index 59eb7a059f35b7..0bdbe8c2e2bc97 100644 --- a/Doc/library/pprint.rst +++ b/Doc/library/pprint.rst @@ -31,8 +31,7 @@ Functions --------- .. function:: pp(object, stream=None, indent=4, width=88, depth=None, *, \ - compact=False, expand=True, sort_dicts=False, \ - underscore_numbers=False) + compact=False, sort_dicts=False, underscore_numbers=False) Prints the formatted representation of *object*, followed by a newline. This function may be used in the interactive interpreter @@ -66,22 +65,12 @@ Functions :param bool compact: Control the way long :term:`sequences ` are formatted. - If ``True``, as many items as will fit within the *width* - will be formatted on each output line. - If ``False`` (the default), each item of a sequence - will be formatted on a separate line, - *unless* *expand* mode is also active (which is now the default); - to opt out of *expand* mode, pass ``expand=False`` rather than - relying on ``compact=False``. - Takes precedence over *expand*. - - :param bool expand: - If ``True`` (the default), + If ``False`` (the default), opening parentheses and brackets will be followed by a newline and the following content will be indented by one level, similar to - pretty-printed JSON. Has no effect when *compact* is also ``True``. - Pass ``expand=False`` to restore the pre-3.15 layout where each - item appears on its own line without the extra indentation. + pretty-printed JSON. + If ``True``, as many items as will fit within the *width* + will be formatted on each output line. :param bool sort_dicts: If ``True``, dictionaries will be formatted with @@ -103,8 +92,7 @@ Functions .. function:: pprint(object, stream=None, indent=4, width=88, depth=None, *, \ - compact=False, expand=True, sort_dicts=True, \ - underscore_numbers=False) + compact=False, sort_dicts=True, underscore_numbers=False) Alias for :func:`~pprint.pp` with *sort_dicts* set to ``True`` by default, which would automatically sort the dictionaries' keys, @@ -112,11 +100,10 @@ Functions .. function:: pformat(object, indent=4, width=88, depth=None, *, \ - compact=False, expand=True, sort_dicts=True, \ - underscore_numbers=False) + compact=False, sort_dicts=True, underscore_numbers=False) Return the formatted representation of *object* as a string. *indent*, - *width*, *depth*, *compact*, *expand*, *sort_dicts* and *underscore_numbers* are + *width*, *depth*, *compact*, *sort_dicts* and *underscore_numbers* are passed to the :class:`PrettyPrinter` constructor as formatting parameters and their meanings are as described in the documentation above. @@ -160,7 +147,7 @@ PrettyPrinter objects .. index:: single: ...; placeholder .. class:: PrettyPrinter(indent=4, width=88, depth=None, stream=None, *, \ - compact=False, expand=True, sort_dicts=True, \ + compact=False, sort_dicts=True, \ underscore_numbers=False) Construct a :class:`PrettyPrinter` instance. @@ -181,7 +168,7 @@ PrettyPrinter objects 'knights', 'ni', ] - >>> pp = pprint.PrettyPrinter(indent=1, width=41, compact=True, expand=False) + >>> pp = pprint.PrettyPrinter(indent=1, width=41, compact=True) >>> pp.pprint(stuff) [['spam', 'eggs', 'lumberjack', 'knights', 'ni'], @@ -222,14 +209,12 @@ PrettyPrinter objects .. versionchanged:: 3.11 No longer attempts to write to :data:`!sys.stdout` if it is ``None``. - .. versionchanged:: 3.15 - Added the *expand* parameter. - .. versionchanged:: next - Changed default *indent* from 1 to 4, - default *width* from 80 to 88, - and default *expand* from ``False`` to ``True``. - *compact* takes precedence over *expand* when both are ``True``. + Changed default *indent* from 1 to 4 + and default *width* from 80 to 88. + The default ``compact=False`` layout is now similar to + pretty-printed JSON, with opening parentheses and brackets + followed by a newline and the contents indented by one level. :class:`PrettyPrinter` instances have the following methods: @@ -447,60 +432,3 @@ cannot be split, the specified width will be exceeded:: 'summary': 'A sample Python project', 'version': '1.2.0', } - -The *expand* format (similar to pretty-printed JSON) is the default. -To disable it, pass ``expand=False``:: - - >>> pprint.pp(project_info, expand=False) - { 'author': 'The Python Packaging Authority', - 'author_email': 'pypa-dev@googlegroups.com', - 'bugtrack_url': None, - 'classifiers': [ 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Topic :: Software Development :: Build Tools'], - 'description': 'A sample Python project\n' - '=======================\n' - '\n' - 'This is the description file for the project.\n' - '\n' - 'The file should use UTF-8 encoding and be written using ' - 'ReStructured Text. It\n' - 'will be used to generate the project webpage on PyPI, and should ' - 'be written for\n' - 'that purpose.\n' - '\n' - 'Typical contents for this file would include an overview of the ' - 'project, basic\n' - 'usage examples, etc. Generally, including the project changelog in ' - 'here is not\n' - 'a good idea, although a simple "What\'s New" section for the most ' - 'recent version\n' - 'may be appropriate.', - 'description_content_type': None, - 'docs_url': None, - 'download_url': 'UNKNOWN', - 'downloads': {'last_day': -1, 'last_month': -1, 'last_week': -1}, - 'home_page': 'https://github.com/pypa/sampleproject', - 'keywords': 'sample setuptools development', - 'license': 'MIT', - 'maintainer': None, - 'maintainer_email': None, - 'name': 'sampleproject', - 'package_url': 'https://pypi.org/project/sampleproject/', - 'platform': 'UNKNOWN', - 'project_url': 'https://pypi.org/project/sampleproject/', - 'project_urls': { 'Download': 'UNKNOWN', - 'Homepage': 'https://github.com/pypa/sampleproject'}, - 'release_url': 'https://pypi.org/project/sampleproject/1.2.0/', - 'requires_dist': None, - 'requires_python': None, - 'summary': 'A sample Python project', - 'version': '1.2.0'} diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index eb91bae31941fb..685c1333afb306 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1087,16 +1087,11 @@ pickletools pprint ------ -* Add an *expand* keyword argument for :func:`pprint.pprint`, - :func:`pprint.pformat`, :func:`pprint.pp`. If true, the output will be - formatted similar to pretty-printed :func:`json.dumps` when - *indent* is supplied. +* :mod:`pprint` now uses modern defaults: ``indent=4, width=88``, + and the default ``compact=False`` output is now formatted similar to + pretty-printed :func:`json.dumps`. (Contributed by Stefan Todoran, Semyon Moroz and Hugo van Kemenade in - :gh:`112632`.) - -* :mod:`pprint` now uses modern defaults: ``indent=4, width=88, expand=True``. - Use ``indent=1, width=80, expand=False`` to retain old output. - (Contributed by Hugo van Kemenade in :gh:`149189`.) + :gh:`112632` and :gh:`149189`.) * Add t-string support to :mod:`pprint`. (Contributed by Loïc Simon and Hugo van Kemenade in :gh:`134551`.) diff --git a/Lib/pprint.py b/Lib/pprint.py index 8575a36e325967..1fd7e3ec95a073 100644 --- a/Lib/pprint.py +++ b/Lib/pprint.py @@ -51,14 +51,13 @@ def pprint( depth=None, *, compact=False, - expand=True, sort_dicts=True, underscore_numbers=False, ): """Pretty-print a Python object to a stream [default is sys.stdout].""" printer = PrettyPrinter( stream=stream, indent=indent, width=width, depth=depth, - compact=compact, expand=expand, sort_dicts=sort_dicts, + compact=compact, sort_dicts=sort_dicts, underscore_numbers=underscore_numbers) printer.pprint(object) @@ -70,13 +69,12 @@ def pformat( depth=None, *, compact=False, - expand=True, sort_dicts=True, underscore_numbers=False, ): """Format a Python object into a pretty-printed representation.""" return PrettyPrinter(indent=indent, width=width, depth=depth, - compact=compact, expand=expand, sort_dicts=sort_dicts, + compact=compact, sort_dicts=sort_dicts, underscore_numbers=underscore_numbers).pformat(object) @@ -137,7 +135,6 @@ def __init__( stream=None, *, compact=False, - expand=True, sort_dicts=True, underscore_numbers=False, ): @@ -159,13 +156,6 @@ def __init__( compact If true, several items will be combined in one line. - Takes precedence over expand mode. The default (false) no longer - disables expand mode on its own; pass ``expand=False`` for that. - - expand - If true (the default), the output will be formatted similar - to pretty-printed json.dumps() when ``indent`` is supplied. - Has no effect if compact mode is also enabled. sort_dicts If true, dict keys are sorted. @@ -190,7 +180,6 @@ def __init__( else: self._stream = _sys.stdout self._compact = bool(compact) - self._expand = bool(expand) and not self._compact self._sort_dicts = sort_dicts self._underscore_numbers = underscore_numbers @@ -243,36 +232,36 @@ def _format(self, object, stream, indent, allowance, context, level): stream.write(rep) def _format_block_start(self, start_str, indent): - if self._expand: - return f"{start_str}\n{' ' * indent}" - return start_str + if self._compact: + return start_str + return f"{start_str}\n{' ' * indent}" def _format_block_end(self, end_str, indent): - if self._expand: - return f"\n{' ' * indent}{end_str}" - return end_str + if self._compact: + return end_str + return f"\n{' ' * indent}{end_str}" def _child_indent(self, indent, prefix_len): - if self._expand: - return indent - return indent + prefix_len + if self._compact: + return indent + prefix_len + return indent def _write_indent_padding(self, write): - if self._expand: - if self._indent_per_level > 0: - write(self._indent_per_level * " ") - elif self._indent_per_level > 1: - write((self._indent_per_level - 1) * " ") + if self._compact: + if self._indent_per_level > 1: + write((self._indent_per_level - 1) * " ") + elif self._indent_per_level > 0: + write(self._indent_per_level * " ") def _pprint_dataclass(self, object, stream, indent, allowance, context, level): # Lazy import to improve module import time from dataclasses import fields as dataclass_fields cls_name = object.__class__.__name__ - if self._expand: - indent += self._indent_per_level - else: + if self._compact: indent += len(cls_name) + 1 + else: + indent += self._indent_per_level items = [(f.name, getattr(object, f.name)) for f in dataclass_fields(object) if f.repr] stream.write(self._format_block_start(cls_name + '(', indent)) self._format_namespace_items(items, stream, indent, allowance, context, level) @@ -395,7 +384,7 @@ def _pprint_list(self, object, stream, indent, allowance, context, level): def _pprint_tuple(self, object, stream, indent, allowance, context, level): stream.write(self._format_block_start('(', indent)) - if len(object) == 1 and not self._expand: + if len(object) == 1 and self._compact: endchar = ',)' else: endchar = ')' @@ -416,7 +405,7 @@ def _pprint_set(self, object, stream, indent, allowance, context, level): else: stream.write(self._format_block_start(typ.__name__ + '({', indent)) endchar = '})' - if not self._expand: + if self._compact: indent += len(typ.__name__) + 1 object = sorted(object, key=_safe_key) self._format_items(object, stream, indent, allowance + len(endchar), @@ -434,10 +423,10 @@ def _pprint_str(self, object, stream, indent, allowance, context, level): chunks = [] lines = object.splitlines(True) if level == 1: - if self._expand: - indent += self._indent_per_level - else: + if self._compact: indent += 1 + else: + indent += self._indent_per_level allowance += 1 max_width1 = max_width = self._width - indent for i, line in enumerate(lines): @@ -490,10 +479,10 @@ def _pprint_bytes(self, object, stream, indent, allowance, context, level): return parens = level == 1 if parens: - if self._expand: - indent += self._indent_per_level - else: + if self._compact: indent += 1 + else: + indent += self._indent_per_level allowance += 1 write(self._format_block_start('(', indent)) delim = '' @@ -510,11 +499,11 @@ def _pprint_bytes(self, object, stream, indent, allowance, context, level): def _pprint_bytearray(self, object, stream, indent, allowance, context, level): write = stream.write write(self._format_block_start('bytearray(', indent)) - if self._expand: + if self._compact: + recursive_indent = indent + 10 + else: write(' ' * self._indent_per_level) recursive_indent = indent + self._indent_per_level - else: - recursive_indent = indent + 10 self._pprint_bytes(bytes(object), stream, recursive_indent, allowance + 1, context, level + 1) write(self._format_block_end(')', indent)) @@ -542,10 +531,10 @@ def _pprint_simplenamespace(self, object, stream, indent, allowance, context, le cls_name = 'namespace' else: cls_name = object.__class__.__name__ - if self._expand: - indent += self._indent_per_level - else: + if self._compact: indent += len(cls_name) + 1 + else: + indent += self._indent_per_level items = object.__dict__.items() stream.write(self._format_block_start(cls_name + '(', indent)) self._format_namespace_items(items, stream, indent, allowance, context, @@ -575,7 +564,7 @@ def _format_dict_items(self, items, stream, indent, allowance, context, ) if not last: write(delimnl) - elif self._expand: + elif not self._compact: write(',') def _format_namespace_items(self, items, stream, indent, allowance, context, level): @@ -601,7 +590,7 @@ def _format_namespace_items(self, items, stream, indent, allowance, context, lev ) if not last: write(delimnl) - elif self._expand: + elif not self._compact: write(',') def _format_items(self, items, stream, indent, allowance, context, level): @@ -643,7 +632,7 @@ def _format_items(self, items, stream, indent, allowance, context, level): self._format(ent, stream, indent, allowance if last else 1, context, level) - if last and self._expand: + if last and not self._compact: write(',') def _repr(self, object, context, level): @@ -668,11 +657,11 @@ def _pprint_default_dict(self, object, stream, indent, allowance, context, level return rdf = self._repr(object.default_factory, context, level) cls = object.__class__ - if self._expand: - stream.write('%s(%s, ' % (cls.__name__, rdf)) - else: + if self._compact: indent += len(cls.__name__) + 1 stream.write('%s(%s,\n%s' % (cls.__name__, rdf, ' ' * indent)) + else: + stream.write('%s(%s, ' % (cls.__name__, rdf)) self._pprint_dict(object, stream, indent, allowance + 1, context, level) stream.write(')') @@ -706,14 +695,14 @@ def _pprint_chain_map(self, object, stream, indent, allowance, context, level): cls = object.__class__ stream.write(self._format_block_start(cls.__name__ + '(', indent + self._indent_per_level)) - if self._expand: - indent += self._indent_per_level - else: + if self._compact: indent += len(cls.__name__) + 1 + else: + indent += self._indent_per_level for i, m in enumerate(object.maps): if i == len(object.maps) - 1: self._format(m, stream, indent, allowance + 1, context, level) - if self._expand: + if not self._compact: stream.write(',') stream.write(self._format_block_end(')', indent - self._indent_per_level)) else: @@ -728,7 +717,7 @@ def _pprint_deque(self, object, stream, indent, allowance, context, level): return cls = object.__class__ stream.write(self._format_block_start(cls.__name__ + '([', indent)) - if not self._expand: + if self._compact: indent += len(cls.__name__) + 1 if object.maxlen is None: self._format_items(object, stream, indent, allowance + 2, @@ -738,10 +727,10 @@ def _pprint_deque(self, object, stream, indent, allowance, context, level): self._format_items(object, stream, indent, 2, context, level) rml = self._repr(object.maxlen, context, level) - if self._expand: - stream.write('%s], maxlen=%s)' % ('\n' + ' ' * indent, rml)) - else: + if self._compact: stream.write('],\n%smaxlen=%s)' % (' ' * indent, rml)) + else: + stream.write('%s], maxlen=%s)' % ('\n' + ' ' * indent, rml)) _dispatch[_collections.deque.__repr__] = _pprint_deque @@ -762,10 +751,10 @@ def _pprint_user_string(self, object, stream, indent, allowance, context, level) def _pprint_template(self, object, stream, indent, allowance, context, level): cls_name = object.__class__.__name__ - if self._expand: - indent += self._indent_per_level - else: + if self._compact: indent += len(cls_name) + 1 + else: + indent += self._indent_per_level items = ( ("strings", object.strings), @@ -781,7 +770,20 @@ def _pprint_template(self, object, stream, indent, allowance, context, level): def _pprint_interpolation(self, object, stream, indent, allowance, context, level): cls_name = object.__class__.__name__ - if self._expand: + if self._compact: + indent += len(cls_name) + items = ( + object.value, + object.expression, + object.conversion, + object.format_spec, + ) + stream.write(cls_name + "(") + self._format_items( + items, stream, indent, allowance, context, level + ) + stream.write(")") + else: indent += self._indent_per_level items = ( ("value", object.value), @@ -796,19 +798,6 @@ def _pprint_interpolation(self, object, stream, indent, allowance, context, leve stream.write( self._format_block_end(")", indent - self._indent_per_level) ) - else: - indent += len(cls_name) - items = ( - object.value, - object.expression, - object.conversion, - object.format_spec, - ) - stream.write(cls_name + "(") - self._format_items( - items, stream, indent, allowance, context, level - ) - stream.write(")") t = t"{0}" _dispatch[type(t).__repr__] = _pprint_template diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py index f4a81963b79760..d66758d3acd7be 100644 --- a/Lib/test/test_pprint.py +++ b/Lib/test/test_pprint.py @@ -16,8 +16,9 @@ from test.support import cpython_only from test.support.import_helper import ensure_lazy_imports -# Pin pre-3.15 defaults for existing formatting tests -_pformat = functools.partial(pprint.pformat, indent=1, width=80, expand=False) +# Pin pre-3.15 width/indent for existing formatting tests. +# compact=True keeps the legacy non-JSON-style container wrapping. +_pformat = functools.partial(pprint.pformat, indent=1, width=80, compact=True) # list, tuple and dict subclasses that do or don't overwrite __repr__ class list2(list): @@ -159,8 +160,7 @@ def test_lazy_import(self): def test_init(self): pp = pprint.PrettyPrinter() pp = pprint.PrettyPrinter(indent=4, width=40, depth=5, - stream=io.StringIO(), compact=True, - expand=False) + stream=io.StringIO(), compact=True) pp = pprint.PrettyPrinter(4, 40, 5, io.StringIO()) pp = pprint.PrettyPrinter(sort_dicts=False) with self.assertRaises(TypeError): @@ -366,74 +366,79 @@ def test_basic_line_wrap(self): self.assertEqual(_pformat(frozendict2(o)), exp) o = range(100) - exp = 'dict_keys([%s])' % ',\n '.join(map(str, o)) + line_ranges = [(0, 22), (22, 42), (42, 62), (62, 82), (82, 100)] + ints = ",\n ".join( + ", ".join(str(i) for i in range(a, b)) for a, b in line_ranges + ) + exp = f"dict_keys([{ints}])" keys = dict.fromkeys(o).keys() self.assertEqual(_pformat(keys), exp) keys = frozendict.fromkeys(o).keys() self.assertEqual(_pformat(keys), exp) - o = range(100) - exp = 'dict_values([%s])' % ',\n '.join(map(str, o)) + exp = f"dict_values([{ints}])" values = {v: v for v in o}.values() self.assertEqual(_pformat(values), exp) values = frozendict({v: v for v in o}).values() self.assertEqual(_pformat(values), exp) - o = range(100) - exp = 'dict_items([%s])' % ',\n '.join("(%s, %s)" % (i, i) for i in o) + line_ranges = [ + (0, 10), (10, 18), (18, 26), (26, 34), (34, 42), (42, 50), (50, 58), + (58, 66), (66, 74), (74, 82), (82, 90), (90, 98), (98, 100), + ] + tups = ",\n ".join( + ", ".join(f"({i}, {i})" for i in range(a, b)) for a, b in line_ranges + ) + exp = f"dict_items([{tups}])" items = {v: v for v in o}.items() self.assertEqual(_pformat(items), exp) items = frozendict({v: v for v in o}).items() self.assertEqual(_pformat(items), exp) - o = range(100) - exp = 'odict_keys([%s])' % ',\n '.join(map(str, o)) + exp = f"odict_keys([{ints}])" keys = collections.OrderedDict.fromkeys(o).keys() self.assertEqual(_pformat(keys), exp) - o = range(100) - exp = 'odict_values([%s])' % ',\n '.join(map(str, o)) + exp = f"odict_values([{ints}])" values = collections.OrderedDict({v: v for v in o}).values() self.assertEqual(_pformat(values), exp) - o = range(100) - exp = 'odict_items([%s])' % ',\n '.join("(%s, %s)" % (i, i) for i in o) + exp = f"odict_items([{tups}])" items = collections.OrderedDict({v: v for v in o}).items() self.assertEqual(_pformat(items), exp) - o = range(100) - exp = 'KeysView({%s})' % (': None,\n '.join(map(str, o)) + ': None') + # KeysView etc. wrap a dict, which always formats one item per line. + none_pairs = ": None,\n ".join(map(str, o)) + ": None" + exp = f"KeysView({{{none_pairs}}})" keys_view = KeysView(dict.fromkeys(o)) self.assertEqual(_pformat(keys_view), exp) - o = range(100) - exp = 'ItemsView({%s})' % (': None,\n '.join(map(str, o)) + ': None') + exp = f"ItemsView({{{none_pairs}}})" items_view = ItemsView(dict.fromkeys(o)) self.assertEqual(_pformat(items_view), exp) - o = range(100) - exp = 'MappingView({%s})' % (': None,\n '.join(map(str, o)) + ': None') + exp = f"MappingView({{{none_pairs}}})" mapping_view = MappingView(dict.fromkeys(o)) self.assertEqual(_pformat(mapping_view), exp) - o = range(100) - exp = 'ValuesView({%s})' % (': None,\n '.join(map(str, o)) + ': None') + exp = f"ValuesView({{{none_pairs}}})" values_view = ValuesView(dict.fromkeys(o)) self.assertEqual(_pformat(values_view), exp) - o = range(100) - exp = '[%s]' % ',\n '.join(map(str, o)) + exp = f"[{ints}]" for type in [list, list2]: self.assertEqual(_pformat(type(o)), exp) - o = tuple(range(100)) - exp = '(%s)' % ',\n '.join(map(str, o)) + exp = f"({ints})" for type in [tuple, tuple2]: self.assertEqual(_pformat(type(o)), exp) # indent parameter - o = range(100) - exp = '[ %s]' % ',\n '.join(map(str, o)) + line_ranges = [(0, 21), (21, 40), (40, 59), (59, 78), (78, 97), (97, 100)] + ints = ",\n ".join( + ", ".join(str(i) for i in range(a, b)) for a, b in line_ranges + ) + exp = f"[ {ints}]" for type in [list, list2]: self.assertEqual(_pformat(type(o), indent=4), exp) @@ -444,13 +449,13 @@ def test_nested_indentations(self): 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, expand=False), expected) + self.assertEqual(pprint.pformat(o, indent=4, width=42, compact=True), 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=41, expand=False), expected) + self.assertEqual(pprint.pformat(o, indent=4, width=41, compact=True), expected) def test_width(self): expected = """\ @@ -468,13 +473,11 @@ def test_width(self): self.assertEqual(_pformat(o, width=16), expected) self.assertEqual(_pformat(o, width=25), expected) self.assertEqual(_pformat(o, width=14), """\ -[[[[[[1, - 2, +[[[[[[1, 2, 3], '1 ' '2']]]], - {1: [1, - 2, + {1: [1, 2, 3], 2: [12, 34]}, @@ -484,8 +487,7 @@ def test_width(self): 'ef',), set2({1, 23}), - [[[[[1, - 2, + [[[[[1, 2, 3], '1 ' '2']]]]]""") @@ -535,37 +537,16 @@ def test_ordered_dict(self): d = collections.OrderedDict(zip(words, itertools.count())) self.assertEqual(_pformat(d), """\ -OrderedDict([('the', 0), - ('quick', 1), - ('brown', 2), - ('fox', 3), - ('jumped', 4), - ('over', 5), - ('a', 6), - ('lazy', 7), - ('dog', 8)])""") - self.assertEqual(_pformat(d.keys(), sort_dicts=False), -"""\ -odict_keys(['the', - 'quick', - 'brown', - 'fox', - 'jumped', - 'over', - 'a', - 'lazy', - 'dog'])""") +OrderedDict([('the', 0), ('quick', 1), ('brown', 2), ('fox', 3), ('jumped', 4), + ('over', 5), ('a', 6), ('lazy', 7), ('dog', 8)])""") + self.assertEqual( + _pformat(d.keys(), sort_dicts=False), + "odict_keys(['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'a', 'lazy', 'dog'])", + ) self.assertEqual(_pformat(d.items(), sort_dicts=False), """\ -odict_items([('the', 0), - ('quick', 1), - ('brown', 2), - ('fox', 3), - ('jumped', 4), - ('over', 5), - ('a', 6), - ('lazy', 7), - ('dog', 8)])""") +odict_items([('the', 0), ('quick', 1), ('brown', 2), ('fox', 3), ('jumped', 4), ('over', 5), + ('a', 6), ('lazy', 7), ('dog', 8)])""") self.assertEqual(_pformat(d.values(), sort_dicts=False), "odict_values([0, 1, 2, 3, 4, 5, 6, 7, 8])") @@ -586,48 +567,80 @@ def test_mapping_proxy(self): d = collections.OrderedDict(zip(words, itertools.count())) m = types.MappingProxyType(d) self.assertEqual(_pformat(m), """\ -mappingproxy(OrderedDict([('the', 0), - ('quick', 1), - ('brown', 2), - ('fox', 3), - ('jumped', 4), - ('over', 5), - ('a', 6), - ('lazy', 7), +mappingproxy(OrderedDict([('the', 0), ('quick', 1), ('brown', 2), ('fox', 3), + ('jumped', 4), ('over', 5), ('a', 6), ('lazy', 7), ('dog', 8)]))""") def test_dict_views(self): for dict_class in (dict, collections.OrderedDict, collections.Counter): empty = dict_class({}) short = dict_class(dict(zip('edcba', 'edcba'))) - long = dict_class(dict((chr(x), chr(x)) for x in range(90, 64, -1))) - lengths = {"empty": empty, "short": short, "long": long} + lengths = {"empty": empty, "short": short} prefix = "odict" if dict_class is collections.OrderedDict else "dict" for name, d in lengths.items(): with self.subTest(length=name, prefix=prefix): - is_short = len(d) < 6 - joiner = ", " if is_short else ",\n " k = d.keys() v = d.values() i = d.items() self.assertEqual(_pformat(k, sort_dicts=True), prefix + "_keys([%s])" % - joiner.join(repr(key) for key in sorted(k))) + ", ".join(repr(key) for key in sorted(k))) self.assertEqual(_pformat(v, sort_dicts=True), prefix + "_values([%s])" % - joiner.join(repr(val) for val in sorted(v))) + ", ".join(repr(val) for val in sorted(v))) self.assertEqual(_pformat(i, sort_dicts=True), prefix + "_items([%s])" % - joiner.join(repr(item) for item in sorted(i))) + ", ".join(repr(item) for item in sorted(i))) self.assertEqual(_pformat(k, sort_dicts=False), prefix + "_keys([%s])" % - joiner.join(repr(key) for key in k)) + ", ".join(repr(key) for key in k)) self.assertEqual(_pformat(v, sort_dicts=False), prefix + "_values([%s])" % - joiner.join(repr(val) for val in v)) + ", ".join(repr(val) for val in v)) self.assertEqual(_pformat(i, sort_dicts=False), prefix + "_items([%s])" % - joiner.join(repr(item) for item in i)) + ", ".join(repr(item) for item in i)) + + # Long case: views wrap with compact-mode packing. + long = dict((chr(x), chr(x)) for x in range(90, 64, -1)) + sorted_keys = ( + "['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',\n" + " 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']" + ) + unsorted_keys = ( + "['Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K',\n" + " 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A']" + ) + sorted_items = ( + "[('A', 'A'), ('B', 'B'), ('C', 'C'), ('D', 'D'), ('E', 'E'), ('F', 'F'),\n" + " ('G', 'G'), ('H', 'H'), ('I', 'I'), ('J', 'J'), ('K', 'K'), ('L', 'L'),\n" + " ('M', 'M'), ('N', 'N'), ('O', 'O'), ('P', 'P'), ('Q', 'Q'), ('R', 'R'),\n" + " ('S', 'S'), ('T', 'T'), ('U', 'U'), ('V', 'V'), ('W', 'W'), ('X', 'X'),\n" + " ('Y', 'Y'), ('Z', 'Z')]" + ) + unsorted_items = ( + "[('Z', 'Z'), ('Y', 'Y'), ('X', 'X'), ('W', 'W'), ('V', 'V'), ('U', 'U'),\n" + " ('T', 'T'), ('S', 'S'), ('R', 'R'), ('Q', 'Q'), ('P', 'P'), ('O', 'O'),\n" + " ('N', 'N'), ('M', 'M'), ('L', 'L'), ('K', 'K'), ('J', 'J'), ('I', 'I'),\n" + " ('H', 'H'), ('G', 'G'), ('F', 'F'), ('E', 'E'), ('D', 'D'), ('C', 'C'),\n" + " ('B', 'B'), ('A', 'A')]" + ) + for dict_class in (dict, collections.OrderedDict, collections.Counter): + d = dict_class(long) + prefix = "odict" if dict_class is collections.OrderedDict else "dict" + with self.subTest(length="long", prefix=prefix): + self.assertEqual(_pformat(d.keys(), sort_dicts=True), + f"{prefix}_keys({sorted_keys})") + self.assertEqual(_pformat(d.values(), sort_dicts=True), + f"{prefix}_values({sorted_keys})") + self.assertEqual(_pformat(d.items(), sort_dicts=True), + f"{prefix}_items({sorted_items})") + self.assertEqual(_pformat(d.keys(), sort_dicts=False), + f"{prefix}_keys({unsorted_keys})") + self.assertEqual(_pformat(d.values(), sort_dicts=False), + f"{prefix}_values({unsorted_keys})") + self.assertEqual(_pformat(d.items(), sort_dicts=False), + f"{prefix}_items({unsorted_items})") def test_abc_views(self): empty = {} @@ -764,7 +777,7 @@ def test_simple_namespace(self): lazy=7, dog=8, ) - formatted = pprint.pformat(ns, width=60, indent=4, expand=False) + formatted = pprint.pformat(ns, width=60, indent=4, compact=True) self.assertEqual(formatted, """\ namespace(the=0, quick=1, @@ -813,7 +826,7 @@ def test_small_dataclass(self): def test_larger_dataclass(self): dc = dataclass1("some fairly long text", int(1e10), True) - formatted = pprint.pformat([dc, dc], width=60, indent=4, expand=False) + formatted = pprint.pformat([dc, dc], width=60, indent=4, compact=True) self.assertEqual(formatted, """\ [ dataclass1(field1='some fairly long text', field2=10000000000, @@ -862,7 +875,7 @@ def test_subclassing(self): {'names with spaces': 'should be presented using repr()', others.should.not.be: like.this}""" - dotted_printer = DottedPrettyPrinter(indent=1, expand=False) + dotted_printer = DottedPrettyPrinter(indent=1, compact=True) self.assertEqual(dotted_printer.pformat(o), exp) # length(repr(obj)) < width @@ -877,21 +890,11 @@ def test_set_reprs(self): self.assertEqual(_pformat(set()), 'set()') self.assertEqual(_pformat(set(range(3))), '{0, 1, 2}') self.assertEqual(_pformat(set(range(7)), width=20), '''\ -{0, - 1, - 2, - 3, - 4, - 5, +{0, 1, 2, 3, 4, 5, 6}''') self.assertEqual(_pformat(set2(range(7)), width=20), '''\ -set2({0, - 1, - 2, - 3, - 4, - 5, - 6})''') +set2({0, 1, 2, 3, 4, + 5, 6})''') self.assertEqual(_pformat(set3(range(7)), width=20), 'set3({0, 1, 2, 3, 4, 5, 6})') @@ -899,20 +902,12 @@ def test_set_reprs(self): self.assertEqual(_pformat(frozenset(range(3))), 'frozenset({0, 1, 2})') self.assertEqual(_pformat(frozenset(range(7)), width=20), '''\ -frozenset({0, - 1, - 2, - 3, - 4, - 5, +frozenset({0, 1, 2, + 3, 4, 5, 6})''') self.assertEqual(_pformat(frozenset2(range(7)), width=20), '''\ -frozenset2({0, - 1, - 2, - 3, - 4, - 5, +frozenset2({0, 1, 2, + 3, 4, 5, 6})''') self.assertEqual(_pformat(frozenset3(range(7)), width=20), 'frozenset3({0, 1, 2, 3, 4, 5, 6})') @@ -1163,14 +1158,6 @@ def test_compact(self): [0, 1, 2, 3, 4]]""" self.assertEqual(_pformat(o, width=47, compact=True), expected) - def test_compact_without_explicit_expand(self): - # Passing compact=True alone should not require also passing - # expand=False, even though expand defaults to True. - self.assertEqual( - pprint.pformat([1, 2, 3, 4, 5], width=10, compact=True), - "[ 1, 2,\n 3, 4,\n 5]", - ) - def test_compact_width(self): levels = 20 number = 10 @@ -1347,14 +1334,8 @@ def test_chainmap(self): 'over': 5, 'quick': 1, 'the': 0}, - OrderedDict([('the', 0), - ('quick', 1), - ('brown', 2), - ('fox', 3), - ('jumped', 4), - ('over', 5), - ('a', 6), - ('lazy', 7), + OrderedDict([('the', 0), ('quick', 1), ('brown', 2), ('fox', 3), + ('jumped', 4), ('over', 5), ('a', 6), ('lazy', 7), ('dog', 8)]))""") self.assertEqual(_pformat(d.keys()), """\ @@ -1367,14 +1348,8 @@ def test_chainmap(self): 'over': 5, 'quick': 1, 'the': 0}, - OrderedDict([('the', 0), - ('quick', 1), - ('brown', 2), - ('fox', 3), - ('jumped', 4), - ('over', 5), - ('a', 6), - ('lazy', 7), + OrderedDict([('the', 0), ('quick', 1), ('brown', 2), ('fox', 3), + ('jumped', 4), ('over', 5), ('a', 6), ('lazy', 7), ('dog', 8)])))""") self.assertEqual(_pformat(d.items()), """\ @@ -1387,14 +1362,8 @@ def test_chainmap(self): 'over': 5, 'quick': 1, 'the': 0}, - OrderedDict([('the', 0), - ('quick', 1), - ('brown', 2), - ('fox', 3), - ('jumped', 4), - ('over', 5), - ('a', 6), - ('lazy', 7), + OrderedDict([('the', 0), ('quick', 1), ('brown', 2), ('fox', 3), + ('jumped', 4), ('over', 5), ('a', 6), ('lazy', 7), ('dog', 8)])))""") self.assertEqual(_pformat(d.values()), """\ @@ -1407,14 +1376,8 @@ def test_chainmap(self): 'over': 5, 'quick': 1, 'the': 0}, - OrderedDict([('the', 0), - ('quick', 1), - ('brown', 2), - ('fox', 3), - ('jumped', 4), - ('over', 5), - ('a', 6), - ('lazy', 7), + OrderedDict([('the', 0), ('quick', 1), ('brown', 2), ('fox', 3), + ('jumped', 4), ('over', 5), ('a', 6), ('lazy', 7), ('dog', 8)])))""") def test_deque(self): @@ -1426,25 +1389,13 @@ def test_deque(self): d = collections.deque(zip(words, itertools.count())) self.assertEqual(_pformat(d), """\ -deque([('the', 0), - ('quick', 1), - ('brown', 2), - ('fox', 3), - ('jumped', 4), - ('over', 5), - ('a', 6), - ('lazy', 7), - ('dog', 8)])""") +deque([('the', 0), ('quick', 1), ('brown', 2), ('fox', 3), ('jumped', 4), + ('over', 5), ('a', 6), ('lazy', 7), ('dog', 8)])""") d = collections.deque(zip(words, itertools.count()), maxlen=7) self.assertEqual(_pformat(d), """\ -deque([('brown', 2), - ('fox', 3), - ('jumped', 4), - ('over', 5), - ('a', 6), - ('lazy', 7), - ('dog', 8)], +deque([('brown', 2), ('fox', 3), ('jumped', 4), ('over', 5), ('a', 6), + ('lazy', 7), ('dog', 8)], maxlen=7)""") def test_user_dict(self): @@ -1501,15 +1452,8 @@ def test_user_list(self): d = collections.UserList(zip(words, itertools.count())) self.assertEqual(_pformat(d), """\ -[('the', 0), - ('quick', 1), - ('brown', 2), - ('fox', 3), - ('jumped', 4), - ('over', 5), - ('a', 6), - ('lazy', 7), - ('dog', 8)]""") +[('the', 0), ('quick', 1), ('brown', 2), ('fox', 3), ('jumped', 4), ('over', 5), + ('a', 6), ('lazy', 7), ('dog', 8)]""") def test_user_string(self): d = collections.UserString('') @@ -1562,13 +1506,13 @@ def test_template(self): def test_expand_template(self): d = t"" self.assertEqual( - pprint.pformat(d, expand=True), + pprint.pformat(d), "Template(strings=('',), interpolations=())", ) name = "World" d = t"Hello {name}" self.assertEqual( - pprint.pformat(d, width=40, indent=4, expand=True), + pprint.pformat(d, width=40, indent=4), """\ Template( strings=('Hello ', ''), @@ -1585,7 +1529,7 @@ def test_expand_template(self): ver = {3.13: False, 3.14: True} d = t"Hello { {"name": "Python", "version": ver}!s:z}!" self.assertEqual( - pprint.pformat(d, width=40, indent=4, expand=True), + pprint.pformat(d, width=40, indent=4), """\ Template( strings=('Hello ', '!'), @@ -1626,8 +1570,7 @@ class DummyDataclass: corge=7, garply=(1, 2, 3, 4), ) - self.assertEqual(pprint.pformat(dummy_dataclass, width=40, indent=4, - expand=True), + self.assertEqual(pprint.pformat(dummy_dataclass, width=40, indent=4), """\ DummyDataclass( foo='foo', @@ -1647,8 +1590,7 @@ def test_expand_dict(self): "quux": ["foo", "bar", "baz"], "corge": 7, } - self.assertEqual(pprint.pformat(dummy_dict, width=40, indent=4, - expand=True, sort_dicts=False), + self.assertEqual(pprint.pformat(dummy_dict, width=40, indent=4, sort_dicts=False), """\ { 'foo': 'bar', @@ -1666,8 +1608,7 @@ def test_expand_ordered_dict(self): ("baz", 123), ] ) - self.assertEqual(pprint.pformat(dummy_ordered_dict, width=20, indent=4, - expand=True), + self.assertEqual(pprint.pformat(dummy_ordered_dict, width=20, indent=4), """\ OrderedDict([ ('foo', 1), @@ -1682,8 +1623,7 @@ def test_expand_list(self): "baz", "qux", ] - self.assertEqual(pprint.pformat(dummy_list, width=20, indent=4, - expand=True), + self.assertEqual(pprint.pformat(dummy_list, width=20, indent=4), """\ [ 'foo', @@ -1701,8 +1641,7 @@ def test_expand_tuple(self): 5, 6, ) - self.assertEqual(pprint.pformat(dummy_tuple, width=20, indent=4, - expand=True), + self.assertEqual(pprint.pformat(dummy_tuple, width=20, indent=4), """\ ( 'foo', @@ -1715,7 +1654,7 @@ def test_expand_tuple(self): def test_expand_single_element_tuple(self): self.assertEqual( - pprint.pformat((1,), width=1, indent=4, expand=True), + pprint.pformat((1,), width=1, indent=4), """\ ( 1, @@ -1729,8 +1668,7 @@ def test_expand_set(self): "qux", (1, 2, 3), } - self.assertEqual(pprint.pformat(dummy_set, width=20, indent=4, - expand=True), + self.assertEqual(pprint.pformat(dummy_set, width=20, indent=4), """\ { 'bar', @@ -1753,8 +1691,7 @@ def test_expand_frozenset(self): frozenset(dummy_set), } ) - self.assertEqual(pprint.pformat(dummy_frozenset, width=40, indent=4, - expand=True), + self.assertEqual(pprint.pformat(dummy_frozenset, width=40, indent=4), """\ frozenset({ frozenset({(1, 2, 3)}), @@ -1769,7 +1706,7 @@ def test_expand_frozendict(self): {"foo": "bar", "baz": 123, "qux": [1, 2]} ) self.assertEqual( - pprint.pformat(dummy_frozendict, width=20, indent=4, expand=True), + pprint.pformat(dummy_frozendict, width=20, indent=4), """\ frozendict({ 'baz': 123, @@ -1780,8 +1717,7 @@ def test_expand_frozendict(self): def test_expand_bytes(self): dummy_bytes = b"Hello world! foo bar baz 123 456 789" - self.assertEqual(pprint.pformat(dummy_bytes, width=20, indent=4, - expand=True), + self.assertEqual(pprint.pformat(dummy_bytes, width=20, indent=4), """\ ( b'Hello world!' @@ -1792,8 +1728,7 @@ def test_expand_bytes(self): def test_expand_bytearray(self): dummy_bytes = b"Hello world! foo bar baz 123 456 789" dummy_byte_array = bytearray(dummy_bytes) - self.assertEqual(pprint.pformat(dummy_byte_array, width=40, indent=4, - expand=True), + self.assertEqual(pprint.pformat(dummy_byte_array, width=40, indent=4), """\ bytearray( b'Hello world! foo bar baz 123 456' @@ -1809,8 +1744,7 @@ def test_expand_mappingproxy(self): "corge": 7, } dummy_mappingproxy = types.MappingProxyType(dummy_dict) - self.assertEqual(pprint.pformat(dummy_mappingproxy, width=40, indent=4, - expand=True), + self.assertEqual(pprint.pformat(dummy_mappingproxy, width=40, indent=4), """\ mappingproxy({ 'baz': 123, @@ -1831,8 +1765,7 @@ def test_expand_namespace(self): ), ) - self.assertEqual(pprint.pformat(dummy_namespace, width=40, indent=4, - expand=True), + self.assertEqual(pprint.pformat(dummy_namespace, width=40, indent=4), """\ namespace( foo='bar', @@ -1850,8 +1783,7 @@ def test_expand_defaultdict(self): dummy_defaultdict["foo"].append("baz") dummy_defaultdict["foo"].append("qux") dummy_defaultdict["bar"] = {"foo": "bar", "baz": None} - self.assertEqual(pprint.pformat(dummy_defaultdict, width=40, indent=4, - expand=True), + self.assertEqual(pprint.pformat(dummy_defaultdict, width=40, indent=4), """\ defaultdict(, { 'bar': {'baz': None, 'foo': 'bar'}, @@ -1868,8 +1800,7 @@ def test_expand_counter(self): 'd': 2, 'e': 1, })""" - self.assertEqual(pprint.pformat(dummy_counter, width=40, indent=4, - expand=True), expected) + self.assertEqual(pprint.pformat(dummy_counter, width=40, indent=4), expected) expected2 = """\ Counter({ @@ -1879,8 +1810,7 @@ def test_expand_counter(self): 'd': 2, 'e': 1, })""" - self.assertEqual(pprint.pformat(dummy_counter, width=20, indent=2, - expand=True), expected2) + self.assertEqual(pprint.pformat(dummy_counter, width=20, indent=2), expected2) def test_expand_chainmap(self): dummy_dict = { @@ -1896,8 +1826,7 @@ def test_expand_chainmap(self): {"corge": dummy_dict}, ) dummy_chainmap.maps.append({"garply": "waldo"}) - self.assertEqual(pprint.pformat(dummy_chainmap, width=40, indent=4, - expand=True), + self.assertEqual(pprint.pformat(dummy_chainmap, width=40, indent=4), """\ ChainMap( {'foo': 'bar'}, @@ -1939,8 +1868,7 @@ def test_expand_deque(self): dummy_deque.append(dummy_dict) dummy_deque.extend(dummy_list) dummy_deque.appendleft(dummy_set) - self.assertEqual(pprint.pformat(dummy_deque, width=40, indent=4, - expand=True), + self.assertEqual(pprint.pformat(dummy_deque, width=40, indent=4), """\ deque([ {(1, 2, 3)}, @@ -1971,8 +1899,7 @@ def __init__(self, *args, **kwargs): "corge": 7 }) dummy_userdict.access_count = 5 - self.assertEqual(pprint.pformat(dummy_userdict, width=40, indent=4, - expand=True), + self.assertEqual(pprint.pformat(dummy_userdict, width=40, indent=4), """\ { 'baz': 123, @@ -1992,8 +1919,7 @@ def __init__(self, *args, **kwargs): dummy_userlist = DummyUserList(["first", 2, {"key": "value"}, [4, 5, 6]]) - self.assertEqual(pprint.pformat(dummy_userlist, width=40, indent=4, - expand=True), + self.assertEqual(pprint.pformat(dummy_userlist, width=40, indent=4), """\ [ 'first', @@ -2005,7 +1931,7 @@ def __init__(self, *args, **kwargs): def test_expand_dict_keys(self): d = {"foo": 1, "bar": 2, "baz": 3, "qux": 4, "quux": 5} self.assertEqual( - pprint.pformat(d.keys(), width=20, indent=4, expand=True), + pprint.pformat(d.keys(), width=20, indent=4), """\ dict_keys([ 'bar', @@ -2019,7 +1945,7 @@ def test_expand_dict_keys(self): def test_expand_dict_values(self): d = {"foo": 1, "bar": 2, "baz": 3, "qux": 4, "quux": 5} self.assertEqual( - pprint.pformat(d.values(), width=20, indent=4, expand=True), + pprint.pformat(d.values(), width=20, indent=4), """\ dict_values([ 1, @@ -2033,7 +1959,7 @@ def test_expand_dict_values(self): def test_expand_dict_items(self): d = {"foo": 1, "bar": 2, "baz": 3, "qux": 4, "quux": 5} self.assertEqual( - pprint.pformat(d.items(), width=20, indent=4, expand=True), + pprint.pformat(d.items(), width=20, indent=4), """\ dict_items([ ('bar', 2), @@ -2047,7 +1973,7 @@ def test_expand_dict_items(self): def test_expand_str(self): s = "The quick brown fox jumped over the lazy dog " * 3 self.assertEqual( - pprint.pformat(s, width=40, indent=4, expand=True), + pprint.pformat(s, width=40, indent=4), """\ ( 'The quick brown fox jumped over ' diff --git a/Misc/NEWS.d/next/Library/2026-04-30-18-56-23.gh-issue-149189.mszW10.rst b/Misc/NEWS.d/next/Library/2026-04-30-18-56-23.gh-issue-149189.mszW10.rst index 47511146237f7b..1fd7f76bb25c09 100644 --- a/Misc/NEWS.d/next/Library/2026-04-30-18-56-23.gh-issue-149189.mszW10.rst +++ b/Misc/NEWS.d/next/Library/2026-04-30-18-56-23.gh-issue-149189.mszW10.rst @@ -1,2 +1,7 @@ -:mod:`pprint` now uses modern defaults: ``indent=4, width=88, expand=True``. +:mod:`pprint` now uses modern defaults: ``indent=4`` and ``width=88``, +and the default ``compact=False`` output is now formatted similar to +pretty-printed :func:`json.dumps`, with opening parentheses and brackets +followed by a newline and the contents indented by one level. The +*expand* parameter, added in 3.15.0a8, has been removed; ``compact=False`` +(the default) now produces the former ``expand=True`` layout. Patch by Hugo van Kemenade.