Skip to content

gh-112632: Add optional keyword-only argument expand to pprint#136964

Open
donbarbos wants to merge 51 commits intopython:mainfrom
donbarbos:issue-112632
Open

gh-112632: Add optional keyword-only argument expand to pprint#136964
donbarbos wants to merge 51 commits intopython:mainfrom
donbarbos:issue-112632

Conversation

@donbarbos
Copy link
Copy Markdown
Contributor

@donbarbos donbarbos commented Jul 22, 2025

cc @tomasr8 as the previous reviewer (tests coverage problem fixed)


📚 Documentation preview 📚: https://cpython-previews--136964.org.readthedocs.build/

stodoran and others added 30 commits January 24, 2025 18:19
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
@hugovk
Copy link
Copy Markdown
Member

hugovk commented Mar 24, 2026

Thanks for working on this, it's looking good!

Is "block style JSON formatting" an established term? Wondering if there's a better explanation. Maybe just "pretty-printed JSON" is better?

I've also opened donbarbos#1 to suggest renaming block_style to expand, as it mirrors compact well.

Black uses the word "explode", which I'm not sure fits here, but "expand" is similar.

And let's update from main after merging donbarbos#1, it's been a while since this ran CI.

@donbarbos donbarbos requested a review from AA-Turner as a code owner March 25, 2026 18:43
@hugovk hugovk changed the title gh-112632: Add optional keyword-only argument block_style to pprint gh-112632: Add optional keyword-only argument expand to pprint Mar 25, 2026
Copy link
Copy Markdown
Member

@hugovk hugovk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And see also donbarbos#2.

@hugovk
Copy link
Copy Markdown
Member

hugovk commented Mar 27, 2026

Here's a visual test on a bunch of types:

script
import collections
import dataclasses
import pprint
import types

pp = lambda label, obj: print(
    f"--- {label} ---\n{pprint.pformat(obj, width=20, indent=4, expand=True)}\n"
)

pp("dict", {"foo": "bar", "baz": 123, "qux": [1, 2, 3], "quux": {"a": "b"}})

pp("list", ["foo", "bar", "baz", "qux", "quux"])

pp("tuple", ("foo", "bar", "baz", 4, 5, 6))

pp("tuple (single)", ("only",))

pp("set", {"foo", "bar", "baz", "qux", (1, 2, 3)})

pp("frozenset", frozenset({"foo", "bar", "baz", (1, 2, 3)}))

pp("OrderedDict", collections.OrderedDict([("foo", 1), ("bar", 12), ("baz", 123)]))

dd = collections.defaultdict(list)
dd["foo"].extend(["bar", "baz", "qux"])
dd["bar"] = {"a": "b", "c": None}
pp("defaultdict", dd)

pp("Counter", collections.Counter("abcdeabcdabcaba"))

pp("ChainMap", collections.ChainMap({"foo": "bar"}, {"baz": "qux"}, {"a": [1, 2, 3]}))

pp("deque", collections.deque(["foo", 123, {"a": "b"}, [1, 2, 3]]))

pp("deque (maxlen)", collections.deque(["foo", 123, {"a": "b"}], maxlen=10))


@dataclasses.dataclass
class Point:
    x: float
    y: float
    label: str
    tags: list = dataclasses.field(default_factory=list)


pp("dataclass", Point(1.0, 2.5, "origin", ["a", "b", "c"]))

pp(
    "SimpleNamespace",
    types.SimpleNamespace(foo="bar", count=42, nested=types.SimpleNamespace(x=1, y=2)),
)

pp("bytes", b"Hello world! foo bar baz 123 456 789")

pp("bytearray", bytearray(b"Hello world! foo bar baz 123 456 789"))

pp("mappingproxy", types.MappingProxyType({"foo": "bar", "baz": 123, "qux": [1, 2]}))

pp("frozendict", frozendict({"foo": "bar", "baz": 123, "qux": [1, 2]}))

pp("dict_keys", {"foo": 1, "bar": 2, "baz": 3, "qux": 4, "quux": 5}.keys())

pp("dict_values", {"foo": 1, "bar": 2, "baz": 3, "qux": 4, "quux": 5}.values())

pp("dict_items", {"foo": 1, "bar": 2, "baz": 3, "qux": 4, "quux": 5}.items())

pp("str", "The quick brown fox jumped over the lazy dog " * 3)

pp("UserDict", collections.UserDict({"foo": "bar", "baz": 123, "qux": [1, 2]}))

pp("UserList", collections.UserList(["foo", "bar", "baz", "qux", "quux"]))

pp(
    "UserString",
    collections.UserString("The quick brown fox jumped over the lazy dog " * 3),
)

The new frozendict, other dict_* and str need updating to support expand:

--- frozendict ---
frozendict({
               'baz': 123,
               'foo': 'bar',
               'qux': [
                   1,
                   2
               ]
           })

--- dict_keys ---
dict_keys([       'bar',
    'baz',
    'foo',
    'quux',
    'qux'])

--- dict_values ---
dict_values([       1,
    2,
    3,
    4,
    5])

--- dict_items ---
dict_items([       ('bar', 2),
    ('baz', 3),
    ('foo', 1),
    ('quux', 5),
    ('qux', 4)])

--- str ---
('The quick brown '
 'fox jumped over '
 'the lazy dog The '
 'quick brown fox '
 'jumped over the '
 'lazy dog The '
 'quick brown fox '
 'jumped over the '
 'lazy dog ')
Full output
--- dict ---
{
    'baz': 123,
    'foo': 'bar',
    'quux': {'a': 'b'},
    'qux': [1, 2, 3]
}

--- list ---
[
    'foo',
    'bar',
    'baz',
    'qux',
    'quux'
]

--- tuple ---
(
    'foo',
    'bar',
    'baz',
    4,
    5,
    6
)

--- tuple (single) ---
('only',)

--- set ---
{
    'bar',
    'baz',
    'foo',
    'qux',
    (1, 2, 3)
}

--- frozenset ---
frozenset({
    'bar',
    'baz',
    'foo',
    (1, 2, 3)
})

--- OrderedDict ---
OrderedDict([
    ('foo', 1),
    ('bar', 12),
    ('baz', 123)
])

--- defaultdict ---
defaultdict(<class 'list'>, {
    'bar': {
        'a': 'b',
        'c': None
    },
    'foo': [
        'bar',
        'baz',
        'qux'
    ]
})

--- Counter ---
Counter({
    'a': 5,
    'b': 4,
    'c': 3,
    'd': 2,
    'e': 1
})

--- ChainMap ---
ChainMap(
    {'foo': 'bar'},
    {'baz': 'qux'},
    {
        'a': [1, 2, 3]
    }
)

--- deque ---
deque([
    'foo',
    123,
    {'a': 'b'},
    [1, 2, 3]
])

--- deque (maxlen) ---
deque([
    'foo',
    123,
    {'a': 'b'}
], maxlen=10)

--- dataclass ---
Point(
    x=1.0,
    y=2.5,
    label='origin',
    tags=['a', 'b', 'c']
)

--- SimpleNamespace ---
namespace(
    foo='bar',
    count=42,
    nested=namespace(
        x=1,
        y=2
    )
)

--- bytes ---
(
    b'Hello world!'
    b' foo bar baz'
    b' 123 456 789'
)

--- bytearray ---
bytearray(
    b'Hello world!'
    b' foo bar baz'
    b' 123 456 789'
)

--- mappingproxy ---
mappingproxy({
    'baz': 123,
    'foo': 'bar',
    'qux': [1, 2]
})

--- frozendict ---
frozendict({
               'baz': 123,
               'foo': 'bar',
               'qux': [
                   1,
                   2
               ]
           })

--- dict_keys ---
dict_keys([       'bar',
    'baz',
    'foo',
    'quux',
    'qux'])

--- dict_values ---
dict_values([       1,
    2,
    3,
    4,
    5])

--- dict_items ---
dict_items([       ('bar', 2),
    ('baz', 3),
    ('foo', 1),
    ('quux', 5),
    ('qux', 4)])

--- str ---
('The quick brown '
 'fox jumped over '
 'the lazy dog The '
 'quick brown fox '
 'jumped over the '
 'lazy dog The '
 'quick brown fox '
 'jumped over the '
 'lazy dog ')

--- UserDict ---
{
    'baz': 123,
    'foo': 'bar',
    'qux': [1, 2]
}

--- UserList ---
[
    'foo',
    'bar',
    'baz',
    'qux',
    'quux'
]

--- UserString ---
('The quick brown '
 'fox jumped over '
 'the lazy dog The '
 'quick brown fox '
 'jumped over the '
 'lazy dog The '
 'quick brown fox '
 'jumped over the '
 'lazy dog ')

hugovk and others added 2 commits March 27, 2026 14:37
* Move recursive_indent calculation into helper
* Move indent padding into helper
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
@hugovk
Copy link
Copy Markdown
Member

hugovk commented Mar 28, 2026

Would you like to have a go at adding support for frozendict, dict_* and str or shall I have a look?

@donbarbos
Copy link
Copy Markdown
Contributor Author

Would you like to have a go at adding support for frozendict, dict_* and str or shall I have a look?

I'd like to do it myself, but I don't think I'll be able to devote enough time to it in the near future. So I'd be grateful for help

@hugovk
Copy link
Copy Markdown
Member

hugovk commented Mar 29, 2026

I've also added a trailing comma when expanded, similar to modern Black/Ruff style:

Full output
--- dict ---
{
 'baz': 123,
 'foo': 'bar',
 'quux': {'a': 'b'},
 'qux': [1, 2, 3],
}

--- list ---
[
 'foo',
 'bar',
 'baz',
 'qux',
 'quux',
]

--- tuple ---
(
 'foo',
 'bar',
 'baz',
 4,
 5,
 6,
)

--- tuple (single) ---
('only',)

--- set ---
{
 'bar',
 'baz',
 'foo',
 'qux',
 (1, 2, 3),
}

--- frozenset ---
frozenset({
 'bar',
 'baz',
 'foo',
 (1, 2, 3),
})

--- OrderedDict ---
OrderedDict([
 ('foo', 1),
 ('bar', 12),
 ('baz', 123),
])

--- defaultdict ---
defaultdict(<class 'list'>, {
 'bar': {
  'a': 'b',
  'c': None,
 },
 'foo': [
  'bar',
  'baz',
  'qux',
 ],
})

--- Counter ---
Counter({
 'a': 5,
 'b': 4,
 'c': 3,
 'd': 2,
 'e': 1,
})

--- ChainMap ---
ChainMap(
 {'foo': 'bar'},
 {'baz': 'qux'},
 {'a': [1, 2, 3]},
)

--- deque ---
deque([
 'foo',
 123,
 {'a': 'b'},
 [1, 2, 3],
])

--- deque (maxlen) ---
deque([
 'foo',
 123,
 {'a': 'b'},
], maxlen=10)

--- dataclass ---
Point(
 x=1.0,
 y=2.5,
 label='origin',
 tags=['a', 'b', 'c'],
)

--- SimpleNamespace ---
namespace(
 foo='bar',
 count=42,
 nested=namespace(x=1, y=2),
)

--- bytes ---
(
 b'Hello world! foo'
 b' bar baz 123 456'
 b' 789'
)

--- bytearray ---
bytearray(
 b'Hello world! foo'
 b' bar baz 123 456'
 b' 789'
)

--- mappingproxy ---
mappingproxy({
 'baz': 123,
 'foo': 'bar',
 'qux': [1, 2],
})

--- frozendict ---
frozendict({
 'baz': 123,
 'foo': 'bar',
 'qux': [1, 2],
})

--- dict_keys ---
dict_keys([
 'bar',
 'baz',
 'foo',
 'quux',
 'qux',
])

--- dict_values ---
dict_values([
 1,
 2,
 3,
 4,
 5,
])

--- dict_items ---
dict_items([
 ('bar', 2),
 ('baz', 3),
 ('foo', 1),
 ('quux', 5),
 ('qux', 4),
])

--- str ---
(
 'The quick brown '
 'fox jumped over '
 'the lazy dog The '
 'quick brown fox '
 'jumped over the '
 'lazy dog The '
 'quick brown fox '
 'jumped over the '
 'lazy dog '
)

--- UserDict ---
{
 'baz': 123,
 'foo': 'bar',
 'qux': [1, 2],
}

--- UserList ---
[
 'foo',
 'bar',
 'baz',
 'qux',
 'quux',
]

--- UserString ---
(
 'The quick brown '
 'fox jumped over '
 'the lazy dog The '
 'quick brown fox '
 'jumped over the '
 'lazy dog The '
 'quick brown fox '
 'jumped over the '
 'lazy dog '
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants