Skip to content
Open
25 changes: 20 additions & 5 deletions Doc/library/pprint.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Functions
---------

.. function:: pp(object, stream=None, indent=1, width=80, depth=None, *, \
compact=False, expand=False, sort_dicts=False, \
color=True, compact=False, expand=False, sort_dicts=False, \
underscore_numbers=False)

Prints the formatted representation of *object*, followed by a newline.
Expand Down Expand Up @@ -64,6 +64,12 @@ Functions
on the depth of the objects being formatted.
:type depth: int | None

:param bool color:
If ``True`` (the default), output will be syntax highlighted using ANSI
escape sequences, if the *stream* and :ref:`environment variables
<using-on-controlling-color>` permit.
If ``False``, colored output is always disabled.

:param bool compact:
Control the way long :term:`sequences <sequence>` are formatted.
If ``False`` (the default),
Expand Down Expand Up @@ -101,15 +107,21 @@ Functions

.. versionadded:: 3.8

.. versionchanged:: next
Added the *color* parameter.


.. function:: pprint(object, stream=None, indent=1, width=80, depth=None, *, \
compact=False, expand=False, sort_dicts=True, \
color=True, compact=False, expand=False, sort_dicts=True, \
underscore_numbers=False)

Alias for :func:`~pprint.pp` with *sort_dicts* set to ``True`` by default,
which would automatically sort the dictionaries' keys,
you might want to use :func:`~pprint.pp` instead where it is ``False`` by default.

.. versionchanged:: next
Added the *color* parameter.


.. function:: pformat(object, indent=1, width=80, depth=None, *, \
compact=False, expand=False, sort_dicts=True, \
Expand Down Expand Up @@ -154,14 +166,14 @@ Functions

.. _prettyprinter-objects:

PrettyPrinter Objects
PrettyPrinter objects
---------------------

.. index:: single: ...; placeholder

.. class:: PrettyPrinter(indent=1, width=80, depth=None, stream=None, *, \
compact=False, expand=False, sort_dicts=True, \
underscore_numbers=False)
color=True, compact=False, expand=False, \
sort_dicts=True, underscore_numbers=False)

Construct a :class:`PrettyPrinter` instance.

Expand Down Expand Up @@ -220,6 +232,9 @@ PrettyPrinter Objects
.. versionchanged:: 3.11
No longer attempts to write to :data:`!sys.stdout` if it is ``None``.

.. versionchanged:: next
Added the *color* parameter.

.. versionchanged:: 3.15
Added the *expand* parameter.

Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.15.rst
Comment thread
hugovk marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -989,6 +989,12 @@ pprint
(Contributed by Stefan Todoran, Semyon Moroz and Hugo van Kemenade in
:gh:`112632`.)

* Add *color* parameter to :func:`~pprint.pp` and :func:`~pprint.pprint`.
If ``True`` (the default), output is highlighted in color, when the stream
and :ref:`environment variables <using-on-controlling-color>` permit.
If ``False``, colored output is always disabled.
(Contributed by Hugo van Kemenade in :gh:`145217`.)

* Add t-string support to :mod:`pprint`.
(Contributed by Loïc Simon and Hugo van Kemenade in :gh:`134551`.)

Expand Down
16 changes: 14 additions & 2 deletions Lib/_pyrepl/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,14 @@ def iter_display_chars(
buffer: str,
colors: list[ColorSpan] | None = None,
start_index: int = 0,
*,
escape: bool = True,
) -> Iterator[StyledChar]:
"""Yield visible display characters with widths and semantic color tags.

With ``escape=True`` (default) ASCII control chars are rewritten to caret
notation (``\\n`` -> ``^J``); pass ``escape=False`` to keep them verbatim.

Note: ``colors`` is consumed in place as spans are processed -- callers
that split a buffer across multiple calls rely on this mutation to track
which spans have already been handled.
Expand All @@ -331,7 +336,7 @@ def iter_display_chars(
if colors and color_idx < len(colors) and colors[color_idx].span.start == i:
active_tag = colors[color_idx].tag

if control := _ascii_control_repr(c):
if escape and (control := _ascii_control_repr(c)):
text = control
width = len(control)
elif ord(c) < 128:
Expand Down Expand Up @@ -363,6 +368,8 @@ def disp_str(
colors: list[ColorSpan] | None = None,
start_index: int = 0,
force_color: bool = False,
*,
escape: bool = True,
) -> tuple[CharBuffer, CharWidths]:
r"""Decompose the input buffer into a printable variant with applied colors.

Expand All @@ -374,6 +381,9 @@ def disp_str(
- the second list is the visible width of each character in the input
buffer.

With ``escape=True`` (default) ASCII control chars are rewritten to caret
notation (``\\n`` -> ``^J``); pass ``escape=False`` to keep them verbatim.

Note on colors:
- The `colors` list, if provided, is partially consumed within. We're using
a list and not a generator since we need to hold onto the current
Expand All @@ -393,7 +403,9 @@ def disp_str(
(['\x1b[1;34mw', 'h', 'i', 'l', 'e\x1b[0m', ' ', '1', ':'], [1, 1, 1, 1, 1, 1, 1, 1])

"""
styled_chars = list(iter_display_chars(buffer, colors, start_index))
styled_chars = list(
iter_display_chars(buffer, colors, start_index, escape=escape)
)
chars: CharBuffer = []
char_widths: CharWidths = []
theme = THEME(force_color=force_color)
Expand Down
99 changes: 79 additions & 20 deletions Lib/pprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,41 @@
import sys as _sys
import types as _types
from io import StringIO as _StringIO
lazy import _colorize
lazy import re
lazy from _pyrepl.utils import disp_str, gen_colors
Comment thread
zooba marked this conversation as resolved.
lazy from dataclasses import fields as dataclass_fields
lazy from dataclasses import is_dataclass

__all__ = ["pprint","pformat","isreadable","isrecursive","saferepr",
"PrettyPrinter", "pp"]


def pprint(object, stream=None, indent=1, width=80, depth=None, *,
compact=False, expand=False, sort_dicts=True,
underscore_numbers=False):
def pprint(
object,
stream=None,
indent=1,
width=80,
depth=None,
*,
color=True,
compact=False,
expand=False,
sort_dicts=True,
underscore_numbers=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, expand=expand, sort_dicts=sort_dicts,
underscore_numbers=underscore_numbers)
stream=stream,
indent=indent,
width=width,
depth=depth,
color=color,
compact=compact,
expand=expand,
sort_dicts=sort_dicts,
underscore_numbers=underscore_numbers,
)
printer.pprint(object)


Expand Down Expand Up @@ -111,10 +133,32 @@ def _safe_tuple(t):
return _safe_key(t[0]), _safe_key(t[1])


def _colorize_output(text):
"""Apply syntax highlighting."""
if "\x1b[" in text:
# If the text already contains ANSI escape sequences
# (for example, from a custom __repr__),
# return as-is to avoid breaking their color.
return text
colors = list(gen_colors(text))
chars, _ = disp_str(text, colors=colors, force_color=True, escape=False)
return "".join(chars)


class PrettyPrinter:
def __init__(self, indent=1, width=80, depth=None, stream=None, *,
compact=False, expand=False, sort_dicts=True,
underscore_numbers=False):
def __init__(
self,
indent=1,
width=80,
depth=None,
stream=None,
*,
color=True,
compact=False,
expand=False,
sort_dicts=True,
underscore_numbers=False,
):
"""Handle pretty printing operations onto a stream using a set of
configured parameters.

Expand All @@ -131,6 +175,11 @@ def __init__(self, indent=1, width=80, depth=None, stream=None, *,
The desired output stream. If omitted (or false), the standard
output stream available at construction will be used.

color
If true (the default), syntax highlighting is enabled for pprint
when the stream and environment variables permit.
If false, colored output is always disabled.

compact
If true, several items will be combined in one line.
Incompatible with expand mode.
Expand Down Expand Up @@ -168,11 +217,30 @@ def __init__(self, indent=1, width=80, depth=None, stream=None, *,
self._expand = bool(expand)
self._sort_dicts = sort_dicts
self._underscore_numbers = underscore_numbers
self._color = color
Copy link
Copy Markdown
Member

@sunmy2019 sunmy2019 Apr 28, 2026

Choose a reason for hiding this comment

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

Shall we add a bool(color) conversion just as a few lines above?

        self._compact = bool(compact)
        self._expand = bool(expand)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sure, can do, but not sure if it's necessary?


def pprint(self, object):
if self._stream is not None:
if self._stream is None:
return

use_color = False
if self._color:
try:
if _colorize.can_colorize(file=self._stream):
# Attempt to reify lazy imports, or ImportError
gen_colors, disp_str
use_color = True
except ImportError:
pass

if use_color:
sio = _StringIO()
self._format(object, sio, 0, 0, {}, 0)
self._stream.write(_colorize_output(sio.getvalue()))
else:
self._format(object, self._stream, 0, 0, {}, 0)
self._stream.write("\n")

self._stream.write("\n")

def pformat(self, object):
sio = _StringIO()
Expand All @@ -197,9 +265,6 @@ def _format(self, object, stream, indent, allowance, context, level):
max_width = self._width - indent - allowance
if len(rep) > max_width:
p = self._dispatch.get(type(object).__repr__, None)
# Lazy import to improve module import time
from dataclasses import is_dataclass

if p is not None:
context[objid] = 1
p(self, object, stream, indent, allowance, context, level + 1)
Expand Down Expand Up @@ -240,9 +305,6 @@ def _write_indent_padding(self, write):
write((self._indent_per_level - 1) * " ")

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__
if self._expand:
indent += self._indent_per_level
Expand Down Expand Up @@ -422,9 +484,6 @@ def _pprint_str(self, object, stream, indent, allowance, context, level):
if len(rep) <= max_width1:
chunks.append(rep)
else:
# Lazy import to improve module import time
import re

# A list of alternating (non-space, space) strings
parts = re.findall(r'\S*\s*', line)
assert parts
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,7 @@ def invoke_pickle(self, *flags):
pickle._main(args=[*flags, self.filename])
return self.text_normalize(output.getvalue())

@support.force_not_colorized
def test_invocation(self):
# test 'python -m pickle pickle_file'
data = {
Expand Down
Loading
Loading