mirror of
https://github.com/python/cpython.git
synced 2024-11-27 03:45:08 +08:00
gh-92734: Add indentation feature to reprlib.Repr (GH-92735)
This commit is contained in:
parent
aa3b4cf779
commit
c06c001b30
@ -19,7 +19,7 @@ This module provides a class, an instance, and a function:
|
||||
|
||||
.. class:: Repr(*, maxlevel=6, maxtuple=6, maxlist=6, maxarray=5, maxdict=4, \
|
||||
maxset=6, maxfrozenset=6, maxdeque=6, maxstring=30, maxlong=40, \
|
||||
maxother=30, fillvalue="...")
|
||||
maxother=30, fillvalue="...", indent=None)
|
||||
|
||||
Class which provides formatting services useful in implementing functions
|
||||
similar to the built-in :func:`repr`; size limits for different object types
|
||||
@ -142,6 +142,66 @@ which format specific object types.
|
||||
similar manner as :attr:`maxstring`. The default is ``20``.
|
||||
|
||||
|
||||
.. attribute:: Repr.indent
|
||||
|
||||
If this attribute is set to ``None`` (the default), the output is formatted
|
||||
with no line breaks or indentation, like the standard :func:`repr`.
|
||||
For example:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> example = [
|
||||
1, 'spam', {'a': 2, 'b': 'spam eggs', 'c': {3: 4.5, 6: []}}, 'ham']
|
||||
>>> import reprlib
|
||||
>>> aRepr = reprlib.Repr()
|
||||
>>> print(aRepr.repr(example))
|
||||
[1, 'spam', {'a': 2, 'b': 'spam eggs', 'c': {3: 4.5, 6: []}}, 'ham']
|
||||
|
||||
If :attr:`~Repr.indent` is set to a string, each recursion level
|
||||
is placed on its own line, indented by that string:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> aRepr.indent = '-->'
|
||||
>>> print(aRepr.repr(example))
|
||||
[
|
||||
-->1,
|
||||
-->'spam',
|
||||
-->{
|
||||
-->-->'a': 2,
|
||||
-->-->'b': 'spam eggs',
|
||||
-->-->'c': {
|
||||
-->-->-->3: 4.5,
|
||||
-->-->-->6: [],
|
||||
-->-->},
|
||||
-->},
|
||||
-->'ham',
|
||||
]
|
||||
|
||||
Setting :attr:`~Repr.indent` to a positive integer value behaves as if it
|
||||
was set to a string with that number of spaces:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> aRepr.indent = 4
|
||||
>>> print(aRepr.repr(example))
|
||||
[
|
||||
1,
|
||||
'spam',
|
||||
{
|
||||
'a': 2,
|
||||
'b': 'spam eggs',
|
||||
'c': {
|
||||
3: 4.5,
|
||||
6: [],
|
||||
},
|
||||
},
|
||||
'ham',
|
||||
]
|
||||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
|
||||
.. method:: Repr.repr(obj)
|
||||
|
||||
The equivalent to the built-in :func:`repr` that uses the formatting imposed by
|
||||
|
@ -38,7 +38,7 @@ class Repr:
|
||||
def __init__(
|
||||
self, *, maxlevel=6, maxtuple=6, maxlist=6, maxarray=5, maxdict=4,
|
||||
maxset=6, maxfrozenset=6, maxdeque=6, maxstring=30, maxlong=40,
|
||||
maxother=30, fillvalue='...',
|
||||
maxother=30, fillvalue='...', indent=None,
|
||||
):
|
||||
self.maxlevel = maxlevel
|
||||
self.maxtuple = maxtuple
|
||||
@ -52,6 +52,7 @@ class Repr:
|
||||
self.maxlong = maxlong
|
||||
self.maxother = maxother
|
||||
self.fillvalue = fillvalue
|
||||
self.indent = indent
|
||||
|
||||
def repr(self, x):
|
||||
return self.repr1(x, self.maxlevel)
|
||||
@ -66,6 +67,26 @@ class Repr:
|
||||
else:
|
||||
return self.repr_instance(x, level)
|
||||
|
||||
def _join(self, pieces, level):
|
||||
if self.indent is None:
|
||||
return ', '.join(pieces)
|
||||
if not pieces:
|
||||
return ''
|
||||
indent = self.indent
|
||||
if isinstance(indent, int):
|
||||
if indent < 0:
|
||||
raise ValueError(
|
||||
f'Repr.indent cannot be negative int (was {indent!r})'
|
||||
)
|
||||
indent *= ' '
|
||||
try:
|
||||
sep = ',\n' + (self.maxlevel - level + 1) * indent
|
||||
except TypeError as error:
|
||||
raise TypeError(
|
||||
f'Repr.indent must be a str, int or None, not {type(indent)}'
|
||||
) from error
|
||||
return sep.join(('', *pieces, ''))[1:-len(indent) or None]
|
||||
|
||||
def _repr_iterable(self, x, level, left, right, maxiter, trail=''):
|
||||
n = len(x)
|
||||
if level <= 0 and n:
|
||||
@ -76,8 +97,8 @@ class Repr:
|
||||
pieces = [repr1(elem, newlevel) for elem in islice(x, maxiter)]
|
||||
if n > maxiter:
|
||||
pieces.append(self.fillvalue)
|
||||
s = ', '.join(pieces)
|
||||
if n == 1 and trail:
|
||||
s = self._join(pieces, level)
|
||||
if n == 1 and trail and self.indent is None:
|
||||
right = trail + right
|
||||
return '%s%s%s' % (left, s, right)
|
||||
|
||||
@ -124,7 +145,7 @@ class Repr:
|
||||
pieces.append('%s: %s' % (keyrepr, valrepr))
|
||||
if n > self.maxdict:
|
||||
pieces.append(self.fillvalue)
|
||||
s = ', '.join(pieces)
|
||||
s = self._join(pieces, level)
|
||||
return '{%s}' % (s,)
|
||||
|
||||
def repr_str(self, x, level):
|
||||
|
@ -9,6 +9,7 @@ import shutil
|
||||
import importlib
|
||||
import importlib.util
|
||||
import unittest
|
||||
import textwrap
|
||||
|
||||
from test.support import verbose
|
||||
from test.support.os_helper import create_empty_file
|
||||
@ -39,6 +40,7 @@ class ReprTests(unittest.TestCase):
|
||||
"maxlong": 110,
|
||||
"maxother": 111,
|
||||
"fillvalue": "x" * 112,
|
||||
"indent": "x" * 113,
|
||||
}
|
||||
r1 = Repr()
|
||||
for attr, val in example_kwargs.items():
|
||||
@ -246,6 +248,338 @@ class ReprTests(unittest.TestCase):
|
||||
r(y)
|
||||
r(z)
|
||||
|
||||
def test_valid_indent(self):
|
||||
test_cases = [
|
||||
{
|
||||
'object': (),
|
||||
'tests': (
|
||||
(dict(indent=None), '()'),
|
||||
(dict(indent=False), '()'),
|
||||
(dict(indent=True), '()'),
|
||||
(dict(indent=0), '()'),
|
||||
(dict(indent=1), '()'),
|
||||
(dict(indent=4), '()'),
|
||||
(dict(indent=4, maxlevel=2), '()'),
|
||||
(dict(indent=''), '()'),
|
||||
(dict(indent='-->'), '()'),
|
||||
(dict(indent='....'), '()'),
|
||||
),
|
||||
},
|
||||
{
|
||||
'object': '',
|
||||
'tests': (
|
||||
(dict(indent=None), "''"),
|
||||
(dict(indent=False), "''"),
|
||||
(dict(indent=True), "''"),
|
||||
(dict(indent=0), "''"),
|
||||
(dict(indent=1), "''"),
|
||||
(dict(indent=4), "''"),
|
||||
(dict(indent=4, maxlevel=2), "''"),
|
||||
(dict(indent=''), "''"),
|
||||
(dict(indent='-->'), "''"),
|
||||
(dict(indent='....'), "''"),
|
||||
),
|
||||
},
|
||||
{
|
||||
'object': [1, 'spam', {'eggs': True, 'ham': []}],
|
||||
'tests': (
|
||||
(dict(indent=None), '''\
|
||||
[1, 'spam', {'eggs': True, 'ham': []}]'''),
|
||||
(dict(indent=False), '''\
|
||||
[
|
||||
1,
|
||||
'spam',
|
||||
{
|
||||
'eggs': True,
|
||||
'ham': [],
|
||||
},
|
||||
]'''),
|
||||
(dict(indent=True), '''\
|
||||
[
|
||||
1,
|
||||
'spam',
|
||||
{
|
||||
'eggs': True,
|
||||
'ham': [],
|
||||
},
|
||||
]'''),
|
||||
(dict(indent=0), '''\
|
||||
[
|
||||
1,
|
||||
'spam',
|
||||
{
|
||||
'eggs': True,
|
||||
'ham': [],
|
||||
},
|
||||
]'''),
|
||||
(dict(indent=1), '''\
|
||||
[
|
||||
1,
|
||||
'spam',
|
||||
{
|
||||
'eggs': True,
|
||||
'ham': [],
|
||||
},
|
||||
]'''),
|
||||
(dict(indent=4), '''\
|
||||
[
|
||||
1,
|
||||
'spam',
|
||||
{
|
||||
'eggs': True,
|
||||
'ham': [],
|
||||
},
|
||||
]'''),
|
||||
(dict(indent=4, maxlevel=2), '''\
|
||||
[
|
||||
1,
|
||||
'spam',
|
||||
{
|
||||
'eggs': True,
|
||||
'ham': [],
|
||||
},
|
||||
]'''),
|
||||
(dict(indent=''), '''\
|
||||
[
|
||||
1,
|
||||
'spam',
|
||||
{
|
||||
'eggs': True,
|
||||
'ham': [],
|
||||
},
|
||||
]'''),
|
||||
(dict(indent='-->'), '''\
|
||||
[
|
||||
-->1,
|
||||
-->'spam',
|
||||
-->{
|
||||
-->-->'eggs': True,
|
||||
-->-->'ham': [],
|
||||
-->},
|
||||
]'''),
|
||||
(dict(indent='....'), '''\
|
||||
[
|
||||
....1,
|
||||
....'spam',
|
||||
....{
|
||||
........'eggs': True,
|
||||
........'ham': [],
|
||||
....},
|
||||
]'''),
|
||||
),
|
||||
},
|
||||
{
|
||||
'object': {
|
||||
1: 'two',
|
||||
b'three': [
|
||||
(4.5, 6.7),
|
||||
[set((8, 9)), frozenset((10, 11))],
|
||||
],
|
||||
},
|
||||
'tests': (
|
||||
(dict(indent=None), '''\
|
||||
{1: 'two', b'three': [(4.5, 6.7), [{8, 9}, frozenset({10, 11})]]}'''),
|
||||
(dict(indent=False), '''\
|
||||
{
|
||||
1: 'two',
|
||||
b'three': [
|
||||
(
|
||||
4.5,
|
||||
6.7,
|
||||
),
|
||||
[
|
||||
{
|
||||
8,
|
||||
9,
|
||||
},
|
||||
frozenset({
|
||||
10,
|
||||
11,
|
||||
}),
|
||||
],
|
||||
],
|
||||
}'''),
|
||||
(dict(indent=True), '''\
|
||||
{
|
||||
1: 'two',
|
||||
b'three': [
|
||||
(
|
||||
4.5,
|
||||
6.7,
|
||||
),
|
||||
[
|
||||
{
|
||||
8,
|
||||
9,
|
||||
},
|
||||
frozenset({
|
||||
10,
|
||||
11,
|
||||
}),
|
||||
],
|
||||
],
|
||||
}'''),
|
||||
(dict(indent=0), '''\
|
||||
{
|
||||
1: 'two',
|
||||
b'three': [
|
||||
(
|
||||
4.5,
|
||||
6.7,
|
||||
),
|
||||
[
|
||||
{
|
||||
8,
|
||||
9,
|
||||
},
|
||||
frozenset({
|
||||
10,
|
||||
11,
|
||||
}),
|
||||
],
|
||||
],
|
||||
}'''),
|
||||
(dict(indent=1), '''\
|
||||
{
|
||||
1: 'two',
|
||||
b'three': [
|
||||
(
|
||||
4.5,
|
||||
6.7,
|
||||
),
|
||||
[
|
||||
{
|
||||
8,
|
||||
9,
|
||||
},
|
||||
frozenset({
|
||||
10,
|
||||
11,
|
||||
}),
|
||||
],
|
||||
],
|
||||
}'''),
|
||||
(dict(indent=4), '''\
|
||||
{
|
||||
1: 'two',
|
||||
b'three': [
|
||||
(
|
||||
4.5,
|
||||
6.7,
|
||||
),
|
||||
[
|
||||
{
|
||||
8,
|
||||
9,
|
||||
},
|
||||
frozenset({
|
||||
10,
|
||||
11,
|
||||
}),
|
||||
],
|
||||
],
|
||||
}'''),
|
||||
(dict(indent=4, maxlevel=2), '''\
|
||||
{
|
||||
1: 'two',
|
||||
b'three': [
|
||||
(...),
|
||||
[...],
|
||||
],
|
||||
}'''),
|
||||
(dict(indent=''), '''\
|
||||
{
|
||||
1: 'two',
|
||||
b'three': [
|
||||
(
|
||||
4.5,
|
||||
6.7,
|
||||
),
|
||||
[
|
||||
{
|
||||
8,
|
||||
9,
|
||||
},
|
||||
frozenset({
|
||||
10,
|
||||
11,
|
||||
}),
|
||||
],
|
||||
],
|
||||
}'''),
|
||||
(dict(indent='-->'), '''\
|
||||
{
|
||||
-->1: 'two',
|
||||
-->b'three': [
|
||||
-->-->(
|
||||
-->-->-->4.5,
|
||||
-->-->-->6.7,
|
||||
-->-->),
|
||||
-->-->[
|
||||
-->-->-->{
|
||||
-->-->-->-->8,
|
||||
-->-->-->-->9,
|
||||
-->-->-->},
|
||||
-->-->-->frozenset({
|
||||
-->-->-->-->10,
|
||||
-->-->-->-->11,
|
||||
-->-->-->}),
|
||||
-->-->],
|
||||
-->],
|
||||
}'''),
|
||||
(dict(indent='....'), '''\
|
||||
{
|
||||
....1: 'two',
|
||||
....b'three': [
|
||||
........(
|
||||
............4.5,
|
||||
............6.7,
|
||||
........),
|
||||
........[
|
||||
............{
|
||||
................8,
|
||||
................9,
|
||||
............},
|
||||
............frozenset({
|
||||
................10,
|
||||
................11,
|
||||
............}),
|
||||
........],
|
||||
....],
|
||||
}'''),
|
||||
),
|
||||
},
|
||||
]
|
||||
for test_case in test_cases:
|
||||
with self.subTest(test_object=test_case['object']):
|
||||
for repr_settings, expected_repr in test_case['tests']:
|
||||
with self.subTest(repr_settings=repr_settings):
|
||||
r = Repr()
|
||||
for attribute, value in repr_settings.items():
|
||||
setattr(r, attribute, value)
|
||||
resulting_repr = r.repr(test_case['object'])
|
||||
expected_repr = textwrap.dedent(expected_repr)
|
||||
self.assertEqual(resulting_repr, expected_repr)
|
||||
|
||||
def test_invalid_indent(self):
|
||||
test_object = [1, 'spam', {'eggs': True, 'ham': []}]
|
||||
test_cases = [
|
||||
(-1, (ValueError, '[Nn]egative|[Pp]ositive')),
|
||||
(-4, (ValueError, '[Nn]egative|[Pp]ositive')),
|
||||
((), (TypeError, None)),
|
||||
([], (TypeError, None)),
|
||||
((4,), (TypeError, None)),
|
||||
([4,], (TypeError, None)),
|
||||
(object(), (TypeError, None)),
|
||||
]
|
||||
for indent, (expected_error, expected_msg) in test_cases:
|
||||
with self.subTest(indent=indent):
|
||||
r = Repr()
|
||||
r.indent = indent
|
||||
expected_msg = expected_msg or f'{type(indent)}'
|
||||
with self.assertRaisesRegex(expected_error, expected_msg):
|
||||
r.repr(test_object)
|
||||
|
||||
def write_file(path, text):
|
||||
with open(path, 'w', encoding='ASCII') as fp:
|
||||
fp.write(text)
|
||||
|
@ -0,0 +1 @@
|
||||
Allow multi-element reprs emitted by :mod:`reprlib` to be pretty-printed using configurable indentation.
|
Loading…
Reference in New Issue
Block a user