Skip to content
Merged
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 103 additions & 71 deletions peps/pep-0813.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ content. However, the existing :mod:`pprint` module can only format builtin obj
By providing a way for classes to customize how their instances participate in pretty printing,
users have more options for visually improving the display of their complex data, especially for debugging.

By extending the built-in :func:`print` function to automatically pretty print its output, debugging with
user-friendly display is made even more convenient. Since no extra imports are required, users can easily
just piggyback on well-worn "print debugging" patterns, at least for the most common use cases.
By adding a ``!p`` conversion specifier to f-strings and ``str.format()``, debugging with user-friendly
display is made even more convenient. Since no extra imports are required, users can easily just piggyback
on well-worn "print debugging" patterns, at least for the most common use cases.

These extensions work both independently and complimentary, to provide powerful new use cases.

Expand Down Expand Up @@ -70,8 +70,11 @@ class name. The printed representation will usually look like a class construct
keyword, and default arguments. The values can be any of the following formats:

* A single value, representing a positional argument. The value itself is used.
* A 2-tuple of ``(name, value)`` representing a keyword argument. A representation of
``name=value`` is used.
* A 2-tuple of ``(name, value)`` representing a keyword argument. A
representation of ``name=value`` is used. If ``name`` is "false-y", then
``value`` is treated as a positional argument. This is how you would print
a positional argument with a tuple value. See :ref:`examples`. Otherwise,
``name`` **MUST** exactly be a ``str``.
* A 3-tuple of ``(name, value, default_value)`` representing a keyword argument with a default
value. If ``value`` equals ``default_value``, then this tuple is skipped, otherwise
``name=value`` is used.
Comment thread
warsaw marked this conversation as resolved.
Outdated
Expand All @@ -82,29 +85,40 @@ keyword, and default arguments. The values can be any of the following formats:
<https://rich.readthedocs.io/en/latest/pretty.html#rich-repr-protocol>`_.


A new argument to built-in ``print``
------------------------------------
Additions to ``f-strings`` and ``str.format()``
-----------------------------------------------

Built-in :func:`print` takes a new optional argument, appended to the end of the argument list, called
``pretty``, which can take one of the following values:
In addition to the existing ``!s``, ``!r``, and ``!a`` conversion specifiers, a new ``!p``
conversion specifier will be added. The effect of this specifier with an expression ``value`` will
be to call :py:func:`python:pprint.pformat` (importing the ``pprint`` module as needed), passing
``value`` as the only argument.

* ``None`` - the default. No pretty printing is invoked. Fully backward compatible.
* ``True`` - use a temporary instance of the :py:class:`python:pprint.PrettyPrinter` class to get a
pretty representation of the object.
* An instance with a ``pformat()`` method, which has the same signature as
:py:meth:`python:pprint.PrettyPrinter.pformat`. When given, this will usually be an instance of a
subclass of ``PrettyPrinter`` with its ``pformat()`` method overridden. Note that this form
requires **an instance** of a pretty printer, not a class, as only ``print(..., pretty=True)``
performs implicit instantiation.
For f-strings only, the ``!p`` conversion field accepts an optional "format spec" expression, after
Comment thread
warsaw marked this conversation as resolved.
Outdated
the normal separating ``:``, for example: ``f'{obj!p:expression}'``. Formally, the expression can
be anything that evaluates to a callable accepting a single argument (the object to format), and
returns a string which is used as the f-string substitution value. Also for f-strings, the ``!p``
Comment thread
warsaw marked this conversation as resolved.
specifier is fully compatible with the ``obj=`` form, e.g. ``f'{obj=!p:expression}'``.

Note that ``:expression`` format specs are *not* allowed in ``str.format()`` calls, at least for the
Comment thread
warsaw marked this conversation as resolved.
Outdated
initial implementation of this PEP.


Additions to the C-API
----------------------

To support ``!p``, a new function, ``PyObject_Pretty()`` is added to the
`Limited C API <https://docs.python.org/3/c-api/stable.html#limited-c-api>`_.
This function takes two arguments: a ``PyObject *`` for the object to pretty
print, and an optional ``PyObject *`` for the formatter callable (which may be
``NULL``). When the formatter is ``NULL``, this function imports the ``pprint``
module and calls :func:`pprint.pformat` with the object as its argument,
returning the results. When the formatter is not ``NULL``, it must be a
callable that accepts the object as its single argument and returns a string;
this is used to support the already-evaluated ``:expression`` in
``f'{obj!p:expression}'``.

Additions to ``f-strings`` and ``str.format()``
-----------------------------------------------

In addition to the existing ``!s``, ``!r``, and ``!a`` conversion specifiers, an additional ``!p`` conversion
will be added. The effect of this specifier with an expression ``value`` will be to call
:py:func:`python:pprint.pformat`, passing ``value`` as the only argument. In this initial specification, it
will be an error to provide any format specifier if ``!p`` is used.
.. _examples:

Examples
========
Expand All @@ -125,7 +139,7 @@ class:
yield 'pickups', self._pickups
yield 'active', self._active, False

Now let's create a couple of instances, and pretty print them:
Now let's create a couple of instances and pretty print them:

.. code-block:: pycon

Expand All @@ -137,42 +151,49 @@ Now let's create a couple of instances, and pretty print them:
>>> pprint.pprint(stingray)
Bass(5, pickups='humbucker', active=True)

Here's an example of using the ``pretty`` argument to built-in ``print()``:
The ``!p`` conversion specifier can be used in f-strings and ``str.format()`` to pretty print values:

.. code-block:: pycon
:force:

>>> import os
Comment thread
warsaw marked this conversation as resolved.
>>> print(os.pathconf_names)
{'PC_ASYNC_IO': 17, 'PC_CHOWN_RESTRICTED': 7, 'PC_FILESIZEBITS': 18, 'PC_LINK_MAX': 1, 'PC_MAX_CANON': 2, 'PC_MAX_INPUT': 3, 'PC_NAME_MAX': 4, 'PC_NO_TRUNC': 8, 'PC_PATH_MAX': 5, 'PC_PIPE_BUF': 6, 'PC_PRIO_IO': 19, 'PC_SYNC_IO': 25, 'PC_VDISABLE': 9, 'PC_MIN_HOLE_SIZE': 27, 'PC_ALLOC_SIZE_MIN': 16, 'PC_REC_INCR_XFER_SIZE': 20, 'PC_REC_MAX_XFER_SIZE': 21, 'PC_REC_MIN_XFER_SIZE': 22, 'PC_REC_XFER_ALIGN': 23, 'PC_SYMLINK_MAX': 24}

>>> print(os.pathconf_names, pretty=True)
{'PC_ALLOC_SIZE_MIN': 16,
'PC_ASYNC_IO': 17,
'PC_CHOWN_RESTRICTED': 7,
'PC_FILESIZEBITS': 18,
'PC_LINK_MAX': 1,
'PC_MAX_CANON': 2,
'PC_MAX_INPUT': 3,
'PC_MIN_HOLE_SIZE': 27,
'PC_NAME_MAX': 4,
'PC_NO_TRUNC': 8,
'PC_PATH_MAX': 5,
'PC_PIPE_BUF': 6,
'PC_PRIO_IO': 19,
'PC_REC_INCR_XFER_SIZE': 20,
'PC_REC_MAX_XFER_SIZE': 21,
'PC_REC_MIN_XFER_SIZE': 22,
'PC_REC_XFER_ALIGN': 23,
'PC_SYMLINK_MAX': 24,
'PC_SYNC_IO': 25,
'PC_VDISABLE': 9}
>>> print(f'{precision=!p}')
precision=Bass(4, pickups='split coil P')

>>> print('{!p}'.format(precision))
Bass(4, pickups='split coil P')

For f-strings only, the ``!p`` conversion field also accepts a format spec expression, which must
Comment thread
warsaw marked this conversation as resolved.
Outdated
evaluate to a callable taking a single argument and returning a string:

.. code-block:: pycon
:force:

>>> def slappa(da: Bass) -> str:
... return 'All about that bass'

>>> print(f'{precision=!p:slappa}')
precision=All about that bass

Here's an example where a positional argument has a tuple value. In this case, you use the 2-tuple format,
with the ``name`` being "false-y".

.. code-block:: pycon

>>> class Things:
... def __pprint__(self):
... yield (None, (1, 2))
... yield ('', (3, 4))
... yield ('arg', (5, 6))
...
>>> from rich.pretty import pprint
>>> pprint(Things())
Things((1, 2), (3, 4), arg=(5, 6))


Backwards Compatibility
=======================

When none of the new features are used, this PEP is fully backward compatible, both for built-in
``print()`` and the ``pprint`` module.
When none of the new features are used, this PEP is fully backward compatible.


Security Implications
Expand All @@ -184,7 +205,7 @@ There are no known security implications for this proposal.
How to Teach This
=================

Documentation and examples are added to the ``pprint`` module and the ``print()`` function.
Documentation and examples are added to the ``pprint`` module, f-strings, and ``str.format()``.
Beginners don't need to be taught these new features until they want prettier representations of
their objects.

Expand All @@ -205,10 +226,12 @@ None at this time.
Open Issues
===========

The output format and APIs are heavily inspired by `Rich
<rich-repr-protocol_>`_. The idea is that Rich could
implement an API compatible with ``print(..., pretty=RichPrinter)`` fairly easily. Rich's API is designed to
print constructor-like representations of instances, which means that it's not possible to control much of the
Rich compatibility
------------------

The output format and APIs are heavily inspired by `Rich <rich-repr-protocol_>`_. The idea is that Rich could
implement a callable compatible with ``!p:callable`` fairly easily. Rich's API is designed to print
constructor-like representations of instances, which means that it's not possible to control much of the
"class chrome" around the arguments. Rich does support using angle brackets (i.e. ``<...>``) instead of
parentheses by setting the attribute ``.angular=True`` on the rich repr method. This PEP does not support
that feature, although it likely could in the future.
Expand All @@ -217,32 +240,41 @@ This also means that there's no way to control the pretty printed format of buil
dicts, lists, etc. This seems fine as ``pprint`` is not intended to be as feature-rich (pun intended!) as
Rich. This PEP purposefully deems such fancy features as out-of-scope.

One consequence of ``print(..., pretty=True)`` is that it can be more less obvious if you wanted to print
multiple objects with, say a newline between the object representations. Compare these two outputs:

.. code-block:: pycon

>>> print(precision, '\n', stingray, pretty=True)
Bass(4, pickups='split coil P') '\n' Bass(5, pickups='humbucker', active=True)

>>> print(precision, stingray, sep='\n', pretty=True)
Bass(4, pickups='split coil P')
Bass(5, pickups='humbucker', active=True)
Deferred Ideas
==============

It's likely you'll want the second output, but more complicated multi-object displays could get even less
convenient and/or more verbose.
In the future, we could add support for ``!p`` conversions to t-strings. Addition of the ``:expression``
format for ``!p`` conversions on ``str.format()`` is also deferred.


Acknowledgments
===============

TBD
Pablo Galindo Salgado for helping the PEP authors prototype the use of and prove the feasibility of
``!p:callable`` for f-strings.


Footnotes
=========

TBD
None at this time.


Change History
==============

* `TBD <TBD>`__

* For f-strings only (not ``str.format()``) the ``!p`` conversion field takes an optional "format spec".
* The PEP no longer proposes a ``pretty`` argument to the ``print()`` built-in function. With the
addition of ``!p:callable`` syntax for f-strings, the new argument is unnecessary.
* Specify that to pretty print tuples as positional arguments, use the 2-tuple value format, passing
a "false-y" value as the argument name.
* Clarify that a truth-y ``name`` must be a ``str``.
* Specify that the ``!p`` conversion in f-strings and ``str.format()`` implicitly perform an
import of the ``pprint`` module.
* Describe the new Limited C API function ``PyObject_Pretty()``, and add the optional argument.


Copyright
Expand Down
Loading