Skip to content

gh-145153: Implement PEP 813 pprint protocol - #161647

Draft
ericvsmith wants to merge 22 commits into
python:mainfrom
ericvsmith:PEP-813-pprint
Draft

ericvsmith wants to merge 22 commits into
python:mainfrom
ericvsmith:PEP-813-pprint

Conversation

@ericvsmith

@ericvsmith ericvsmith commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

ericvsmith and others added 22 commits October 20, 2026 18:43
collections replaces its pure-Python OrderedDict with the C version from
_collections, so the __pprint__() added to the Python class never applied.
The C class defines its own __repr__ and only inherited dict.__pprint__, so
describe() fell back to repr() and test__pprint failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The built-in containers now have __pprint__() methods, so the example uses
plain dicts, lists and frozensets instead of subclasses that add one.  It
prints with f'{obj!p:{Printer(**options)}}' and imports Printer and Expand
from _pprint, dropping its private copy of the vocabulary, describe() and
the printer.  That copy couldn't read the _pprint objects the built-ins
describe themselves with.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PEP 813: use built-in __pprint__ in the example, fix OrderedDict
…eaking functionality available for user types.
* Restore the public PyObject_Pretty() and have it join the lines.

PEP 813 specifies PyObject_Pretty() in the limited C API.  It calls the
printer (the default one when printer is NULL), joins the lines it returns
with '\n', and returns the str, mirroring PyObject_Str() and friends.  It
raises TypeError if the printer returns a str, whose items are characters
rather than lines, or if any line is not a str.

FORMAT_PRETTY and str.format() now call it, so the join and its checks live
in one place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Don't generate a dataclass __pprint__() when the class defines __repr__().

Both methods would be defined on the same class, so the MRO rule would use
the generated __pprint__(), which describes every field, instead of the
hand-written __repr__(), which may hide some of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Bring _pprint in line with the PEP 813 draft.

* Re-export the protocol's names from pprint, lazily, so that importing
  pprint stays cheap.
* Add pprint.LEAF, which decompose() returns for a leaf, and
  pprint.DescriptionType.
* Add pprint.FormattableNumber.  int and float delegate to it, and the
  Printer formats it with its new number_format option, a format spec.
  If format() raises, the number is printed with repr().
* Add a Layout.hug attribute.  When exactly one item is a container, the
  other items stay on the opening and closing lines, as PrettyPrinter does
  with expand=True.  defaultdict, mappingproxy, the MappingViews and deque
  use it.
* Split strings and bytes when rendering, so that the split depends on the
  column, as in PrettyPrinter.  Only a literal that is the whole object is
  parenthesized.  The continuation lines of a nested literal are aligned
  under its first piece.
* Match PrettyPrinter for bytearray and deque, print OrderedDict as
  OrderedDict({...}), use '(...)' for tuple recursion, and describe an
  Interpolation positionally, to agree with repr().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Regenerate Programs/test_frozenmain.h for the new FORMAT_PRETTY opcode.

This fixes test_embed's test_frozenmain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Render the default markers through a dispatch table in the Printer.

The Printer now looks up each marker in a table that maps a marker class to
the function that renders it, with BreakableString, BreakableBytes and
FormattableNumber as the default entries.  This is the design of PEP 813's
deferred idea of a user-supplied marker table, without the public option.

A rendering function is called as render(marker, *, width, last_width,
top_level) and returns an iterable of lines.  width is the room for each
line from the column where the marker starts, and last_width is the room
for the last line, which leaves space for any text that follows.  The
printer places the first line at the current position and the rest at the
marker's starting column.  With unlimited width a function returns the
marker's one-line form, which is what the printer measures when it decides
whether a container fits.  A marker that isn't in the table is printed with
repr().

The markers are now plain leaves in the structural document, as
decompose() reports.  Since a dataclass that generates __repr__() now also
generates __pprint__(), the marker classes define __repr__() themselves so
that they stay leaves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Have string.templatelib.convert() pretty print the way !p does.

convert(obj, 'p', printer) joined the printer's lines itself, so it skipped
the checks that PyObject_Pretty() makes.  A printer that returned a str gave
one line per character instead of raising TypeError.  convert() now
evaluates the same f-string that the template would, so both share one path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Fix PEP 813 printer regressions after the C migration

* Use hanging indentation for leaf marker name lookup
… module (#5)

* PEP 813: sort keyword items last, and treat a None printer as the default

Sorting a sortable layout that contains keyword items failed with
TypeError.  Keyword items are now sorted by name after the other items,
so the output stays in call order, as in Example('y', 'z', a=10, b=0).

PyObject_Pretty() now treats a printer of None like NULL and uses the
default printer.  So f'{obj!p:{None}}' matches f'{obj!p}' and
string.templatelib.convert(obj, 'p', None), where None already meant
that no printer was given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Move the PEP 813 protocol into _pprint_protocol and break the import cycles

The protocol code lived inside _load_protocol(), which a module
__getattr__ ran on first use and then copied into the module globals.
It now lives at module level in a new private module, _pprint_protocol,
and pprint re-exports its names with lazy imports.  Importing pprint
alone still does not import dataclasses or the protocol module.

The __pprint__() methods in collections, _collections_abc and
dataclasses imported helpers from pprint inside the function to avoid
import cycles.  The helpers are now inlined into those methods, and
the names they need come from the C _pprint module with lazy imports,
so there is no cycle even when lazy imports are forced eager.

pprint's own function-local imports of dataclasses and re are now lazy
imports at the top of the module.  string.templatelib.convert() no
longer special-cases a printer of None, since PyObject_Pretty() handles
it.  Regenerate stdlib_module_names.h, which also adds the missing
_pprint entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…very mode (#6)

A hugged document was always wrapped in a group, so with Expand.ALWAYS a
container nested in a hugging object such as a deque could stay on one
line.  The hugged document now drops its group below the top level, as
other containers do with ALWAYS.

compact was used only with Expand.NEVER, and was silently ignored with
FIT and ALWAYS.  It now packs positional items in every mode, using
PrettyPrinter's rule: an item whose one-line form fits shares lines with
its packable neighbors, and any other item gets lines of its own.  With
ALWAYS, a non-empty container is never packed.  With NEVER, this also
fixes a broken container starting partway along a line, so the output
now matches PrettyPrinter's, except that a packed line can end at the
last column of the width.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@ericvsmith

Copy link
Copy Markdown
Member Author

I marked this as draft because the last thing I did at the sprint was have Codex convert the builtin type's __pprint__ methods from Python to C, and I haven't had time to review them yet. But I will get to it.

@ericvsmith

Copy link
Copy Markdown
Member Author

This code was originally largely written by Codex, but I spent a lot of time reviewing it, redesigning parts of it, and fixing various errors. As mentioned, I still need to make a pass at the dunder pprint methods that are included.

@rhettinger
rhettinger removed their request for review November 9, 2026 06:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type-feature A feature request or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants