diff --git a/Doc/library/pprint.rst b/Doc/library/pprint.rst index 2985f31bacb47a..918b9c01b4e3f1 100644 --- a/Doc/library/pprint.rst +++ b/Doc/library/pprint.rst @@ -36,7 +36,8 @@ Functions --------- .. function:: pp(object, stream=None, indent=1, width=80, depth=None, *, \ - compact=False, sort_dicts=False, underscore_numbers=False) + compact=False, sort_dicts=False, underscore_numbers=False, \ + block_style=False) Prints the formatted representation of *object*, followed by a newline. This function may be used in the interactive interpreter @@ -85,6 +86,12 @@ Functions integers will be formatted with the ``_`` character for a thousands separator, otherwise underscores are not displayed (the default). + :param bool block_style: + If ``True``, + opening parentheses and brackets will be followed by a newline and the + following content will be indented by one level, similar to block style + JSON formatting. This option is not compatible with *compact*. + >>> import pprint >>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni'] >>> stuff.insert(0, stuff) @@ -100,7 +107,8 @@ Functions .. function:: pprint(object, stream=None, indent=1, width=80, depth=None, *, \ - compact=False, sort_dicts=True, underscore_numbers=False) + compact=False, sort_dicts=True, \ + underscore_numbers=False, block_style=False) Alias for :func:`~pprint.pp` with *sort_dicts* set to ``True`` by default, which would automatically sort the dictionaries' keys, @@ -108,10 +116,11 @@ Functions .. function:: pformat(object, indent=1, width=80, depth=None, *, \ - compact=False, sort_dicts=True, underscore_numbers=False) + compact=False, sort_dicts=True, \ + underscore_numbers=False, block_style=False) Return the formatted representation of *object* as a string. *indent*, - *width*, *depth*, *compact*, *sort_dicts* and *underscore_numbers* are + *width*, *depth*, *compact*, *sort_dicts*, *underscore_numbers* and *block_style* are passed to the :class:`PrettyPrinter` constructor as formatting parameters and their meanings are as described in the documentation above. @@ -155,7 +164,8 @@ PrettyPrinter Objects .. index:: single: ...; placeholder .. class:: PrettyPrinter(indent=1, width=80, depth=None, stream=None, *, \ - compact=False, sort_dicts=True, underscore_numbers=False) + compact=False, sort_dicts=True, \ + underscore_numbers=False, block_style=False) Construct a :class:`PrettyPrinter` instance. @@ -179,6 +189,22 @@ PrettyPrinter Objects 'knights', 'ni'], 'spam', 'eggs', 'lumberjack', 'knights', 'ni'] + >>> pp = pprint.PrettyPrinter(width=41, block_style=True, indent=3) + >>> pp.pprint(stuff) + [ + [ + 'spam', + 'eggs', + 'lumberjack', + 'knights', + 'ni' + ], + 'spam', + 'eggs', + 'lumberjack', + 'knights', + 'ni' + ] >>> tup = ('spam', ('eggs', ('lumberjack', ('knights', ('ni', ('dead', ... ('parrot', ('fresh fruit',)))))))) >>> pp = pprint.PrettyPrinter(depth=6) @@ -198,6 +224,9 @@ PrettyPrinter Objects .. versionchanged:: 3.11 No longer attempts to write to :data:`!sys.stdout` if it is ``None``. + .. versionchanged:: next + Added the *block_style* parameter. + :class:`PrettyPrinter` instances have the following methods: @@ -420,3 +449,72 @@ cannot be split, the specified width will be exceeded:: 'requires_python': None, 'summary': 'A sample Python project', 'version': '1.2.0'} + +Lastly, we can achieve block style formatting with the *block_style* parameter. +Best results are achieved with a higher *indent* value:: + + >>> pprint.pp(project_info, indent=4, block_style=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 + } diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index ac5b53ef94bfb1..df6fef2b1d3835 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -888,6 +888,16 @@ platform (Contributed by Bénédikt Tran in :gh:`122549`.) +pprint +------ + +* Add a *block_style* keyword argument for :func:`pprint.pprint`, + :func:`pprint.pformat`, :func:`pprint.pp`. If true, the output will be + formatted in a block style similar to pretty-printed :func:`json.dumps` when + *indent* is supplied. + (Contributed by Stefan Todoran in :gh:`129274`.) + + pydoc ----- @@ -1200,7 +1210,7 @@ Deprecated .. include:: ../deprecations/pending-removal-in-future.rst Removed -======= +======== argparse -------- diff --git a/Lib/pprint.py b/Lib/pprint.py index dc0953cec67a58..a3503939260300 100644 --- a/Lib/pprint.py +++ b/Lib/pprint.py @@ -44,21 +44,24 @@ def pprint(object, stream=None, indent=1, width=80, depth=None, *, - compact=False, sort_dicts=True, underscore_numbers=False): + compact=False, sort_dicts=True, underscore_numbers=False, + block_style=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, sort_dicts=sort_dicts, - underscore_numbers=underscore_numbers) + underscore_numbers=underscore_numbers, block_style=block_style) printer.pprint(object) def pformat(object, indent=1, width=80, depth=None, *, - compact=False, sort_dicts=True, underscore_numbers=False): + compact=False, sort_dicts=True, underscore_numbers=False, + block_style=False): """Format a Python object into a pretty-printed representation.""" return PrettyPrinter(indent=indent, width=width, depth=depth, compact=compact, sort_dicts=sort_dicts, - underscore_numbers=underscore_numbers).pformat(object) + underscore_numbers=underscore_numbers, + block_style=block_style).pformat(object) def pp(object, *args, sort_dicts=False, **kwargs): @@ -111,7 +114,8 @@ def _safe_tuple(t): class PrettyPrinter: def __init__(self, indent=1, width=80, depth=None, stream=None, *, - compact=False, sort_dicts=True, underscore_numbers=False): + compact=False, sort_dicts=True, underscore_numbers=False, + block_style=False): """Handle pretty printing operations onto a stream using a set of configured parameters. @@ -137,6 +141,11 @@ def __init__(self, indent=1, width=80, depth=None, stream=None, *, underscore_numbers If true, digit groups are separated with underscores. + block_style + If true, the output will be formatted in a block style similar to + pretty-printed json.dumps() when `indent` is supplied. Incompatible + with compact mode. + """ indent = int(indent) width = int(width) @@ -146,6 +155,8 @@ def __init__(self, indent=1, width=80, depth=None, stream=None, *, raise ValueError('depth must be > 0') if not width: raise ValueError('width must be != 0') + if compact and block_style: + raise ValueError('compact and block_style are incompatible') self._depth = depth self._indent_per_level = indent self._width = width @@ -156,6 +167,7 @@ def __init__(self, indent=1, width=80, depth=None, stream=None, *, self._compact = bool(compact) self._sort_dicts = sort_dicts self._underscore_numbers = underscore_numbers + self._block_style = bool(block_style) def pprint(self, object): if self._stream is not None: @@ -205,24 +217,38 @@ def _format(self, object, stream, indent, allowance, context, level): return stream.write(rep) + def _format_block_start(self, start_str, indent): + if self._block_style: + return f"{start_str}\n{' ' * indent}" + else: + return start_str + + def _format_block_end(self, end_str, indent): + if self._block_style: + return f"\n{' ' * indent}{end_str}" + else: + return end_str + 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__ - indent += len(cls_name) + 1 + indent += len(cls_name) + 1 if not self._block_style else self._indent_per_level items = [(f.name, getattr(object, f.name)) for f in dataclass_fields(object) if f.repr] - stream.write(cls_name + '(') + stream.write(self._format_block_start(cls_name + '(', indent)) self._format_namespace_items(items, stream, indent, allowance, context, level) - stream.write(')') + stream.write(self._format_block_end(')', indent - self._indent_per_level)) _dispatch = {} def _pprint_dict(self, object, stream, indent, allowance, context, level): write = stream.write - write('{') - if self._indent_per_level > 1: + write(self._format_block_start('{', indent)) + if self._indent_per_level > 1 and not self._block_style: write((self._indent_per_level - 1) * ' ') + if self._indent_per_level > 0 and self._block_style: + write(self._indent_per_level * ' ') length = len(object) if length: if self._sort_dicts: @@ -231,7 +257,7 @@ def _pprint_dict(self, object, stream, indent, allowance, context, level): items = object.items() self._format_dict_items(items, stream, indent, allowance + 1, context, level) - write('}') + write(self._format_block_end('}', indent)) _dispatch[dict.__repr__] = _pprint_dict @@ -241,27 +267,28 @@ def _pprint_ordered_dict(self, object, stream, indent, allowance, context, level return cls = object.__class__ stream.write(cls.__name__ + '(') + recursive_indent = indent + len(cls.__name__) + 1 if not self._block_style else indent self._format(list(object.items()), stream, - indent + len(cls.__name__) + 1, allowance + 1, + recursive_indent, allowance + 1, context, level) stream.write(')') _dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict def _pprint_list(self, object, stream, indent, allowance, context, level): - stream.write('[') + stream.write(self._format_block_start('[', indent)) self._format_items(object, stream, indent, allowance + 1, context, level) - stream.write(']') + stream.write(self._format_block_end(']', indent)) _dispatch[list.__repr__] = _pprint_list def _pprint_tuple(self, object, stream, indent, allowance, context, level): - stream.write('(') + stream.write(self._format_block_start('(', indent)) endchar = ',)' if len(object) == 1 else ')' self._format_items(object, stream, indent, allowance + len(endchar), context, level) - stream.write(endchar) + stream.write(self._format_block_end(endchar, indent)) _dispatch[tuple.__repr__] = _pprint_tuple @@ -271,16 +298,16 @@ def _pprint_set(self, object, stream, indent, allowance, context, level): return typ = object.__class__ if typ is set: - stream.write('{') + stream.write(self._format_block_start('{', indent)) endchar = '}' else: - stream.write(typ.__name__ + '({') + stream.write(self._format_block_start(typ.__name__ + '({', indent)) endchar = '})' - indent += len(typ.__name__) + 1 + indent += len(typ.__name__) + 1 if not self._block_style else 0 object = sorted(object, key=_safe_key) self._format_items(object, stream, indent, allowance + len(endchar), context, level) - stream.write(endchar) + stream.write(self._format_block_end(endchar, indent)) _dispatch[set.__repr__] = _pprint_set _dispatch[frozenset.__repr__] = _pprint_set @@ -346,9 +373,9 @@ def _pprint_bytes(self, object, stream, indent, allowance, context, level): return parens = level == 1 if parens: - indent += 1 + indent += 1 if not self._block_style else self._indent_per_level allowance += 1 - write('(') + write(self._format_block_start('(', indent)) delim = '' for rep in _wrap_bytes_repr(object, self._width - indent, allowance): write(delim) @@ -356,22 +383,26 @@ def _pprint_bytes(self, object, stream, indent, allowance, context, level): if not delim: delim = '\n' + ' '*indent if parens: - write(')') + write(self._format_block_end(')', indent - self._indent_per_level)) _dispatch[bytes.__repr__] = _pprint_bytes def _pprint_bytearray(self, object, stream, indent, allowance, context, level): write = stream.write - write('bytearray(') - self._pprint_bytes(bytes(object), stream, indent + 10, + write(self._format_block_start('bytearray(', indent)) + if self._block_style: + write(' ' * self._indent_per_level) + recursive_indent = indent + 10 if not self._block_style else indent + self._indent_per_level + self._pprint_bytes(bytes(object), stream, recursive_indent, allowance + 1, context, level + 1) - write(')') + write(self._format_block_end(')', indent)) _dispatch[bytearray.__repr__] = _pprint_bytearray def _pprint_mappingproxy(self, object, stream, indent, allowance, context, level): stream.write('mappingproxy(') - self._format(object.copy(), stream, indent + 13, allowance + 1, + recursive_indent = indent + 13 if not self._block_style else indent + self._format(object.copy(), stream, recursive_indent, allowance + 1, context, level) stream.write(')') @@ -384,11 +415,11 @@ def _pprint_simplenamespace(self, object, stream, indent, allowance, context, le cls_name = 'namespace' else: cls_name = object.__class__.__name__ - indent += len(cls_name) + 1 + indent += len(cls_name) + 1 if not self._block_style else self._indent_per_level items = object.__dict__.items() - stream.write(cls_name + '(') + stream.write(self._format_block_start(cls_name + '(', indent)) self._format_namespace_items(items, stream, indent, allowance, context, level) - stream.write(')') + stream.write(self._format_block_end(')', indent - self._indent_per_level)) _dispatch[_types.SimpleNamespace.__repr__] = _pprint_simplenamespace @@ -403,7 +434,8 @@ def _format_dict_items(self, items, stream, indent, allowance, context, rep = self._repr(key, context, level) write(rep) write(': ') - self._format(ent, stream, indent + len(rep) + 2, + recursive_indent = indent + len(rep) + 2 if not self._block_style else indent + self._format(ent, stream, recursive_indent, allowance if last else 1, context, level) if not last: @@ -422,7 +454,8 @@ def _format_namespace_items(self, items, stream, indent, allowance, context, lev # recursive dataclass repr. write("...") else: - self._format(ent, stream, indent + len(key) + 1, + recursive_indent = indent + len(key) + 1 if not self._block_style else indent + self._format(ent, stream, recursive_indent, allowance if last else 1, context, level) if not last: @@ -431,8 +464,10 @@ def _format_namespace_items(self, items, stream, indent, allowance, context, lev def _format_items(self, items, stream, indent, allowance, context, level): write = stream.write indent += self._indent_per_level - if self._indent_per_level > 1: + if self._indent_per_level > 1 and not self._block_style: write((self._indent_per_level - 1) * ' ') + if self._indent_per_level > 0 and self._block_style: + write(self._indent_per_level * ' ') delimnl = ',\n' + ' ' * indent delim = '' width = max_width = self._width - indent + 1 @@ -491,8 +526,11 @@ def _pprint_default_dict(self, object, stream, indent, allowance, context, level return rdf = self._repr(object.default_factory, context, level) cls = object.__class__ - indent += len(cls.__name__) + 1 - stream.write('%s(%s,\n%s' % (cls.__name__, rdf, ' ' * indent)) + indent += len(cls.__name__) + 1 if not self._block_style else 0 + if self._block_style: + stream.write('%s(%s, ' % (cls.__name__, rdf)) + else: + stream.write('%s(%s,\n%s' % (cls.__name__, rdf, ' ' * indent)) self._pprint_dict(object, stream, indent, allowance + 1, context, level) stream.write(')') @@ -503,14 +541,17 @@ def _pprint_counter(self, object, stream, indent, allowance, context, level): stream.write(repr(object)) return cls = object.__class__ - stream.write(cls.__name__ + '({') - if self._indent_per_level > 1: + stream.write(self._format_block_start(cls.__name__ + '({', indent)) + if self._indent_per_level > 1 and not self._block_style: stream.write((self._indent_per_level - 1) * ' ') + if self._indent_per_level > 0 and self._block_style: + stream.write(self._indent_per_level * ' ') items = object.most_common() + recursive_indent = indent + len(cls.__name__) + 1 if not self._block_style else indent self._format_dict_items(items, stream, - indent + len(cls.__name__) + 1, allowance + 2, + recursive_indent, allowance + 2, context, level) - stream.write('})') + stream.write(self._format_block_end('})', indent)) _dispatch[_collections.Counter.__repr__] = _pprint_counter @@ -519,12 +560,12 @@ def _pprint_chain_map(self, object, stream, indent, allowance, context, level): stream.write(repr(object)) return cls = object.__class__ - stream.write(cls.__name__ + '(') - indent += len(cls.__name__) + 1 + stream.write(self._format_block_start(cls.__name__ + '(', indent + self._indent_per_level)) + indent += len(cls.__name__) + 1 if not self._block_style else 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) - stream.write(')') + stream.write(self._format_block_end(')', indent - self._indent_per_level)) else: self._format(m, stream, indent, 1, context, level) stream.write(',\n' + ' ' * indent) @@ -536,18 +577,20 @@ def _pprint_deque(self, object, stream, indent, allowance, context, level): stream.write(repr(object)) return cls = object.__class__ - stream.write(cls.__name__ + '(') - indent += len(cls.__name__) + 1 - stream.write('[') + stream.write(self._format_block_start(cls.__name__ + '([', indent)) + indent += len(cls.__name__) + 1 if not self._block_style else 0 if object.maxlen is None: self._format_items(object, stream, indent, allowance + 2, context, level) - stream.write('])') + stream.write(self._format_block_end('])', indent)) else: self._format_items(object, stream, indent, 2, context, level) rml = self._repr(object.maxlen, context, level) - stream.write('],\n%smaxlen=%s)' % (' ' * indent, rml)) + if self._block_style: + stream.write('%s], maxlen=%s)' % ('\n' + ' ' * indent, rml)) + else: + stream.write('],\n%smaxlen=%s)' % (' ' * indent, rml)) _dispatch[_collections.deque.__repr__] = _pprint_deque diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py index dfbc2a06e7346f..705e98ae06c08b 100644 --- a/Lib/test/test_pprint.py +++ b/Lib/test/test_pprint.py @@ -141,6 +141,7 @@ 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, block_style=True) def test_basic(self): # Verify .isrecursive() and .isreadable() w/o recursion @@ -1123,6 +1124,361 @@ def test_user_string(self): 'jumped over a ' 'lazy dog'}""") + def test_block_style_dataclass(self): + @dataclasses.dataclass + class DummyDataclass: + foo: str + bar: float + baz: bool + qux: dict = dataclasses.field(default_factory=dict) + quux: list = dataclasses.field(default_factory=list) + corge: int = 1 + garply: tuple = (1, 2, 3, 4) + dummy_dataclass = DummyDataclass( + foo="foo", + bar=1.2, + baz=False, + qux={"foo": "bar", "baz": 123}, + quux=["foo", "bar", "baz"], + corge=7, + garply=(1, 2, 3, 4), + ) + self.assertEqual(pprint.pformat(dummy_dataclass, width=40, indent=4, block_style=True), +"""\ +DummyDataclass( + foo='foo', + bar=1.2, + baz=False, + qux={'baz': 123, 'foo': 'bar'}, + quux=['foo', 'bar', 'baz'], + corge=7, + garply=(1, 2, 3, 4) +)""") + + def test_block_style_dict(self): + dummy_dict = { + "foo": "bar", + "baz": 123, + "qux": {"foo": "bar", "baz": 123}, + "quux": ["foo", "bar", "baz"], + "corge": 7, + } + self.assertEqual(pprint.pformat(dummy_dict, width=40, indent=4, block_style=True, sort_dicts=False), +"""\ +{ + 'foo': 'bar', + 'baz': 123, + 'qux': {'foo': 'bar', 'baz': 123}, + 'quux': ['foo', 'bar', 'baz'], + 'corge': 7 +}""") + + def test_block_style_ordered_dict(self): + dummy_ordered_dict = collections.OrderedDict( + [ + ("foo", 1), + ("bar", 12), + ("baz", 123), + ] + ) + self.assertEqual(pprint.pformat(dummy_ordered_dict, width=20, indent=4, block_style=True), +"""\ +OrderedDict([ + ('foo', 1), + ('bar', 12), + ('baz', 123) +])""") + + def test_block_style_list(self): + dummy_list = [ + "foo", + "bar", + "baz", + "qux", + ] + self.assertEqual(pprint.pformat(dummy_list, width=20, indent=4, block_style=True), +"""\ +[ + 'foo', + 'bar', + 'baz', + 'qux' +]""") + + def test_block_style_tuple(self): + dummy_tuple = ( + "foo", + "bar", + "baz", + 4, + 5, + 6, + ) + self.assertEqual(pprint.pformat(dummy_tuple, width=20, indent=4, block_style=True), +"""\ +( + 'foo', + 'bar', + 'baz', + 4, + 5, + 6 +)""") + + def test_block_style_set(self): + dummy_set = { + "foo", + "bar", + "baz", + "qux", + (1, 2, 3), + } + self.assertEqual(pprint.pformat(dummy_set, width=20, indent=4, block_style=True), +"""\ +{ + 'bar', + 'baz', + 'foo', + 'qux', + (1, 2, 3) +}""") + + def test_block_style_frozenset(self): + dummy_set = { + (1, 2, 3), + } + dummy_frozenset = frozenset( + { + "foo", + "bar", + "baz", + (1, 2, 3), + frozenset(dummy_set), + } + ) + self.assertEqual(pprint.pformat(dummy_frozenset, width=40, indent=4, block_style=True), +"""\ +frozenset({ + frozenset({(1, 2, 3)}), + 'bar', + 'baz', + 'foo', + (1, 2, 3) +})""") + + def test_block_style_bytes(self): + dummy_bytes = b"Hello world! foo bar baz 123 456 789" + self.assertEqual(pprint.pformat(dummy_bytes, width=20, indent=4, block_style=True), +"""\ +( + b'Hello world!' + b' foo bar baz' + b' 123 456 789' +)""") + + def test_block_style_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, block_style=True), +"""\ +bytearray( + b'Hello world! foo bar baz 123 456' + b' 789' +)""") + + def test_block_style_mappingproxy(self): + dummy_dict = { + "foo": "bar", + "baz": 123, + "qux": {"foo": "bar", "baz": 123}, + "quux": ["foo", "bar", "baz"], + "corge": 7, + } + dummy_mappingproxy = types.MappingProxyType(dummy_dict) + self.assertEqual(pprint.pformat(dummy_mappingproxy, width=40, indent=4, block_style=True), +"""\ +mappingproxy({ + 'baz': 123, + 'corge': 7, + 'foo': 'bar', + 'quux': ['foo', 'bar', 'baz'], + 'qux': {'baz': 123, 'foo': 'bar'} +})""") + + def test_block_style_namespace(self): + dummy_namespace = types.SimpleNamespace( + foo="bar", + bar=42, + baz=types.SimpleNamespace( + x=321, + y="string", + d={"foo": True, "bar": "baz"}, + ), + ) + + self.assertEqual(pprint.pformat(dummy_namespace, width=40, indent=4, block_style=True), +"""\ +namespace( + foo='bar', + bar=42, + baz=namespace( + x=321, + y='string', + d={'bar': 'baz', 'foo': True} + ) +)""") + + def test_block_style_defaultdict(self): + dummy_defaultdict = collections.defaultdict(list) + dummy_defaultdict["foo"].append("bar") + 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, block_style=True), +"""\ +defaultdict(, { + 'bar': {'baz': None, 'foo': 'bar'}, + 'foo': ['bar', 'baz', 'qux'] +})""") + + def test_block_style_counter(self): + dummy_counter = collections.Counter("abcdeabcdabcaba") + expected = """\ +Counter({ + 'a': 5, + 'b': 4, + 'c': 3, + 'd': 2, + 'e': 1 +})""" + self.assertEqual(pprint.pformat(dummy_counter, width=40, indent=4, block_style=True), expected) + + expected2 = """\ +Counter({ + 'a': 5, + 'b': 4, + 'c': 3, + 'd': 2, + 'e': 1 +})""" + self.assertEqual(pprint.pformat(dummy_counter, width=20, indent=2, block_style=True), expected2) + + def test_block_style_chainmap(self): + dummy_dict = { + "foo": "bar", + "baz": 123, + "qux": {"foo": "bar", "baz": 123}, + "quux": ["foo", "bar", "baz"], + "corge": 7, + } + dummy_chainmap = collections.ChainMap( + {"foo": "bar"}, + {"baz": "qux"}, + {"corge": dummy_dict}, + ) + dummy_chainmap.maps.append({"garply": "waldo"}) + self.assertEqual(pprint.pformat(dummy_chainmap, width=40, indent=4, block_style=True), +"""\ +ChainMap( + {'foo': 'bar'}, + {'baz': 'qux'}, + { + 'corge': { + 'baz': 123, + 'corge': 7, + 'foo': 'bar', + 'quux': ['foo', 'bar', 'baz'], + 'qux': { + 'baz': 123, + 'foo': 'bar' + } + } + }, + {'garply': 'waldo'} +)""") + + def test_block_style_deque(self): + dummy_dict = { + "foo": "bar", + "baz": 123, + "qux": {"foo": "bar", "baz": 123}, + "quux": ["foo", "bar", "baz"], + "corge": 7, + } + dummy_list = [ + "foo", + "bar", + "baz", + ] + dummy_set = { + (1, 2, 3), + } + dummy_deque = collections.deque(maxlen=10) + dummy_deque.append("foo") + dummy_deque.append(123) + 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, block_style=True), +"""\ +deque([ + {(1, 2, 3)}, + 'foo', + 123, + { + 'baz': 123, + 'corge': 7, + 'foo': 'bar', + 'quux': ['foo', 'bar', 'baz'], + 'qux': {'baz': 123, 'foo': 'bar'} + }, + 'foo', + 'bar', + 'baz' +], maxlen=10)""") + + def test_block_style_userdict(self): + class DummyUserDict(collections.UserDict): + """A custom UserDict with some extra attributes""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.access_count = 0 + dummy_userdict = DummyUserDict({ "foo": "bar", "baz": 123, + "qux": {"foo": "bar", "baz": 123}, + "quux": ["foo", "bar", "baz"], + "corge": 7 }) + dummy_userdict.access_count = 5 + + self.assertEqual(pprint.pformat(dummy_userdict, width=40, indent=4, block_style=True), +"""\ +{ + 'baz': 123, + 'corge': 7, + 'foo': 'bar', + 'quux': ['foo', 'bar', 'baz'], + 'qux': {'baz': 123, 'foo': 'bar'} +}""") + + def test_block_style_userlist(self): + class DummyUserList(collections.UserList): + """A custom UserList with some extra attributes""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.description = "foo" + dummy_userlist = DummyUserList(["first", 2, {"key": "value"}, + [4, 5, 6]]) + + self.assertEqual(pprint.pformat(dummy_userlist, width=40, indent=4, block_style=True), +"""\ +[ + 'first', + 2, + {'key': 'value'}, + [4, 5, 6] +]""") + class DottedPrettyPrinter(pprint.PrettyPrinter): diff --git a/Misc/NEWS.d/next/Library/2025-02-07-00-48-07.gh-issue-112632.95MM0C.rst b/Misc/NEWS.d/next/Library/2025-02-07-00-48-07.gh-issue-112632.95MM0C.rst new file mode 100644 index 00000000000000..b165ce4827cf82 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-02-07-00-48-07.gh-issue-112632.95MM0C.rst @@ -0,0 +1,3 @@ +Add a *block_style* keyword argument for :func:`pprint.pprint`, +:func:`pprint.pformat`, :func:`pprint.pp` by passing on all *kwargs* and +:class:`pprint.PrettyPrinter`. Contributed by Stefan Todoran.