From 4097a92309bf5e89c14128b60d5764c68edaa235 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Thu, 1 May 2025 10:40:58 +0530 Subject: [PATCH 1/5] gh-132855: Use overridden custom format for inherited PrettyPrinter --- Lib/pprint.py | 39 ++++++++++++++++++++++++++++++--------- Lib/test/test_pprint.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/Lib/pprint.py b/Lib/pprint.py index dc0953cec67a58..d4bd27426d880c 100644 --- a/Lib/pprint.py +++ b/Lib/pprint.py @@ -181,30 +181,51 @@ def _format(self, object, stream, indent, allowance, context, level): self._recursive = True self._readable = False return + + # If this is a subclass of PrettyPrinter, force all known + # container types through their pretty-printers so that any + # override of format() applies to their elements. + if type(self) is not PrettyPrinter: + fn = type(object).__repr__ + # skip raw repr for str/bytes + if fn in self._dispatch and not isinstance(object, (str, bytes, bytearray)): + context[objid] = 1 + self._dispatch[fn]( + self, object, stream, + indent, allowance, context, level + 1 + ) + del context[objid] + return + + # fallback to one-line repr + width rep = self._repr(object, 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) + p(self, object, stream, + indent, allowance, context, level + 1) del context[objid] return - elif (is_dataclass(object) and - not isinstance(object, type) and - object.__dataclass_params__.repr and - # Check dataclass has generated repr method. - hasattr(object.__repr__, "__wrapped__") and - "__create_fn__" in object.__repr__.__wrapped__.__qualname__): + elif (is_dataclass(object) + and not isinstance(object, type) + and object.__dataclass_params__.repr + and hasattr(object.__repr__, "__wrapped__") + and "__create_fn__" in object.__repr__.__wrapped__.__qualname__): context[objid] = 1 - self._pprint_dataclass(object, stream, indent, allowance, context, level + 1) + self._pprint_dataclass(object, stream, + indent, allowance, + context, level + 1) del context[objid] return + + # write the one-line repr stream.write(rep) + def _pprint_dataclass(self, object, stream, indent, allowance, context, level): # Lazy import to improve module import time from dataclasses import fields as dataclass_fields diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py index dfbc2a06e7346f..a77e16afc84993 100644 --- a/Lib/test/test_pprint.py +++ b/Lib/test/test_pprint.py @@ -1136,6 +1136,38 @@ def format(self, object, context, maxlevels, level): return pprint.PrettyPrinter.format( self, object, context, maxlevels, level) +# Issue 132855 +SENTINAL = object() +class _hashable: + def __hash__(self): + return 1 + +HASHABLE_SENTINAL = _hashable() +class CustomPrettyPrinter(pprint.PrettyPrinter): + def format(self, obj, context, maxlevels, level): + if obj is SENTINAL: + return "SENTINAL", True, False + elif obj is HASHABLE_SENTINAL: + return "HASHABLE_SENTINAL", True, False + else: + return super().format(obj, context, maxlevels, level) + +class CustomPrettyPrinterTest(unittest.TestCase): + def test_custom_printer(self): + # Test that the custom printer works as expected + obj = SENTINAL + formatted = CustomPrettyPrinter().pformat(obj) + self.assertEqual(formatted, "SENTINAL") + + obj = HASHABLE_SENTINAL + formatted = CustomPrettyPrinter().pformat(obj) + self.assertEqual(formatted, "HASHABLE_SENTINAL") + + # Test that the default printer works as expected + obj = object() + formatted = pprint.pformat(obj) + self.assertNotEqual(formatted, "SENTINAL") + self.assertNotEqual(formatted, "HASHABLE_SENTINAL") if __name__ == "__main__": unittest.main() From 1bd18df7401e434d3098e2214e38347721eb5ddd Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Thu, 1 May 2025 10:45:18 +0530 Subject: [PATCH 2/5] gh-132855: Use overridden custom format for inherited PrettyPrinter --- Lib/pprint.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Lib/pprint.py b/Lib/pprint.py index d4bd27426d880c..5655bd5b43e9b4 100644 --- a/Lib/pprint.py +++ b/Lib/pprint.py @@ -202,23 +202,22 @@ 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) + p(self, object, stream, indent, allowance, context, level + 1) del context[objid] return elif (is_dataclass(object) and not isinstance(object, type) and object.__dataclass_params__.repr + # Check dataclass has generated repr method. and hasattr(object.__repr__, "__wrapped__") and "__create_fn__" in object.__repr__.__wrapped__.__qualname__): context[objid] = 1 - self._pprint_dataclass(object, stream, - indent, allowance, - context, level + 1) + self._pprint_dataclass(object, stream, indent, allowance, context, level + 1) del context[objid] return From f7fa5d275440f9bb89d8175872b14f95cf051cfd Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Thu, 1 May 2025 10:46:59 +0530 Subject: [PATCH 3/5] gh-132855: Use overridden custom format for inherited PrettyPrinter --- Lib/pprint.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/pprint.py b/Lib/pprint.py index 5655bd5b43e9b4..8e92301b2291d2 100644 --- a/Lib/pprint.py +++ b/Lib/pprint.py @@ -210,12 +210,12 @@ def _format(self, object, stream, indent, allowance, context, level): p(self, object, stream, indent, allowance, context, level + 1) del context[objid] return - elif (is_dataclass(object) - and not isinstance(object, type) - and object.__dataclass_params__.repr + elif (is_dataclass(object) and + not isinstance(object, type) and + object.__dataclass_params__.repr and # Check dataclass has generated repr method. - and hasattr(object.__repr__, "__wrapped__") - and "__create_fn__" in object.__repr__.__wrapped__.__qualname__): + hasattr(object.__repr__, "__wrapped__") and + "__create_fn__" in object.__repr__.__wrapped__.__qualname__): context[objid] = 1 self._pprint_dataclass(object, stream, indent, allowance, context, level + 1) del context[objid] From 51ed352dc2bd3182252b4e598c6acd4e124f7ace Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Thu, 1 May 2025 10:47:45 +0530 Subject: [PATCH 4/5] gh-132855: Use overridden custom format for inherited PrettyPrinter --- Lib/pprint.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/pprint.py b/Lib/pprint.py index 8e92301b2291d2..1d6c7fb27753e0 100644 --- a/Lib/pprint.py +++ b/Lib/pprint.py @@ -221,10 +221,8 @@ def _format(self, object, stream, indent, allowance, context, level): del context[objid] return - # write the one-line repr stream.write(rep) - def _pprint_dataclass(self, object, stream, indent, allowance, context, level): # Lazy import to improve module import time from dataclasses import fields as dataclass_fields From 3094c5d69c8b73c8c831cef1b4588fa18c9a7c51 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Thu, 1 May 2025 10:55:10 +0530 Subject: [PATCH 5/5] fix lint --- Lib/pprint.py | 2 +- Lib/test/test_pprint.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/pprint.py b/Lib/pprint.py index 1d6c7fb27753e0..e0dc28fbc7a065 100644 --- a/Lib/pprint.py +++ b/Lib/pprint.py @@ -197,7 +197,7 @@ def _format(self, object, stream, indent, allowance, context, level): del context[objid] return - # fallback to one-line repr + width + # fallback to one-line repr + width rep = self._repr(object, context, level) max_width = self._width - indent - allowance if len(rep) > max_width: diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py index a77e16afc84993..5adb43790ba612 100644 --- a/Lib/test/test_pprint.py +++ b/Lib/test/test_pprint.py @@ -1151,18 +1151,18 @@ def format(self, obj, context, maxlevels, level): return "HASHABLE_SENTINAL", True, False else: return super().format(obj, context, maxlevels, level) - + class CustomPrettyPrinterTest(unittest.TestCase): def test_custom_printer(self): # Test that the custom printer works as expected obj = SENTINAL formatted = CustomPrettyPrinter().pformat(obj) self.assertEqual(formatted, "SENTINAL") - + obj = HASHABLE_SENTINAL formatted = CustomPrettyPrinter().pformat(obj) self.assertEqual(formatted, "HASHABLE_SENTINAL") - + # Test that the default printer works as expected obj = object() formatted = pprint.pformat(obj)