From 4ac21bbe4504a9ebe0d10147d0e805a3f45395f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 14 Jun 2025 12:07:13 +0200 Subject: [PATCH 1/2] fix `reprlib.Repr.repr_int` --- Doc/library/reprlib.rst | 1 - Lib/reprlib.py | 18 ++++++++- Lib/test/test_reprlib.py | 40 +++++++++++++++---- ...-06-14-12-06-55.gh-issue-135487.KdVFff.rst | 3 ++ 4 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-06-14-12-06-55.gh-issue-135487.KdVFff.rst diff --git a/Doc/library/reprlib.rst b/Doc/library/reprlib.rst index 28c7855dfeeef3..e563f5bd4ec46e 100644 --- a/Doc/library/reprlib.rst +++ b/Doc/library/reprlib.rst @@ -219,7 +219,6 @@ which format specific object types. .. method:: Repr.repr_TYPE(obj, level) - :noindex: Formatting methods for specific types are implemented as methods with a name based on the type name. In the method name, **TYPE** is replaced by diff --git a/Lib/reprlib.py b/Lib/reprlib.py index 441d1be4bdede2..0692706984efad 100644 --- a/Lib/reprlib.py +++ b/Lib/reprlib.py @@ -181,7 +181,23 @@ def repr_str(self, x, level): return s def repr_int(self, x, level): - s = builtins.repr(x) # XXX Hope this isn't too slow... + try: + s = builtins.repr(x) + except ValueError as exc: + assert 'sys.set_int_max_str_digits()' in str(exc) + # Those imports must be deferred due to Python's build system + # where the reprlib module is imported before the math module. + import math, sys + # Integers with more than sys.get_int_max_str_digits() digits + # are rendered differently as their repr() raises a ValueError. + # See https://github.com/python/cpython/issues/135487. + k = 1 + int(math.log10(abs(x))) + # When math.log10(abs(x)) is overestimated or underestimated, + # the number of digits should be k - 1 or k + 1 respectively. + # For simplicity, we do not compute the exact number of digits. + max_digits = sys.get_int_max_str_digits() + return (f'<{x.__class__.__name__} instance with roughly {k} ' + f'digits (limit at {max_digits}) at 0x{id(x):x}>') if len(s) > self.maxlong: i = max(0, (self.maxlong-3)//2) j = max(0, self.maxlong-3-i) diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py index 16623654c29b28..7c4fa4b1c57e8f 100644 --- a/Lib/test/test_reprlib.py +++ b/Lib/test/test_reprlib.py @@ -151,14 +151,38 @@ def test_frozenset(self): eq(r(frozenset({1, 2, 3, 4, 5, 6, 7})), "frozenset({1, 2, 3, 4, 5, 6, ...})") def test_numbers(self): - eq = self.assertEqual - eq(r(123), repr(123)) - eq(r(123), repr(123)) - eq(r(1.0/3), repr(1.0/3)) - - n = 10**100 - expected = repr(n)[:18] + "..." + repr(n)[-19:] - eq(r(n), expected) + for x in [123, 1.0 / 3]: + self.assertEqual(r(x), repr(x)) + + max_digits = sys.get_int_max_str_digits() + for k in [100, max_digits - 1]: + with self.subTest(f'10 ** {k}', k=k): + n = 10 ** k + expected = repr(n)[:18] + "..." + repr(n)[-19:] + self.assertEqual(r(n), expected) + + def re_msg(n, d): + return (rf'<{n.__class__.__name__} instance with roughly {d} ' + rf'digits \(limit at {max_digits}\) at 0x[a-f0-9]+>') + + k = max_digits + with self.subTest(f'10 ** {k}', k=k): + n = 10 ** k + self.assertRaises(ValueError, repr, n) + self.assertRegex(r(n), re_msg(n, k + 1)) + + for k in [max_digits + 1, 2 * max_digits]: + self.assertGreater(k, 100) + with self.subTest(f'10 ** {k}', k=k): + n = 10 ** k + self.assertRaises(ValueError, repr, n) + self.assertRegex(r(n), re_msg(n, k + 1)) + with self.subTest(f'10 ** {k} - 1', k=k): + n = 10 ** k - 1 + # For k >> 1, since math.log10(n) == math.log10(n-1), + # the number of digits of n - 1 is overestimated. + self.assertRaises(ValueError, repr, n) + self.assertRegex(r(n), re_msg(n, k + 1)) def test_instance(self): eq = self.assertEqual diff --git a/Misc/NEWS.d/next/Library/2025-06-14-12-06-55.gh-issue-135487.KdVFff.rst b/Misc/NEWS.d/next/Library/2025-06-14-12-06-55.gh-issue-135487.KdVFff.rst new file mode 100644 index 00000000000000..8ded9bc49a5f0b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-14-12-06-55.gh-issue-135487.KdVFff.rst @@ -0,0 +1,3 @@ +Fix :meth:`reprlib.Repr.repr_int ` when given +integers with more than :func:`sys.get_int_max_str_digits` digits. Patch by +Bénédikt Tran. From 813ab8c69cfa47c2d4af62ef3632f2a8bff2e1c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 15 Jun 2025 10:26:43 +0200 Subject: [PATCH 2/2] update comment --- Lib/reprlib.py | 5 ++--- Lib/test/test_reprlib.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Lib/reprlib.py b/Lib/reprlib.py index 0692706984efad..ab18247682b69a 100644 --- a/Lib/reprlib.py +++ b/Lib/reprlib.py @@ -192,9 +192,8 @@ def repr_int(self, x, level): # are rendered differently as their repr() raises a ValueError. # See https://github.com/python/cpython/issues/135487. k = 1 + int(math.log10(abs(x))) - # When math.log10(abs(x)) is overestimated or underestimated, - # the number of digits should be k - 1 or k + 1 respectively. - # For simplicity, we do not compute the exact number of digits. + # Note: math.log10(abs(x)) may be overestimated or underestimated, + # but for simplicity, we do not compute the exact number of digits. max_digits = sys.get_int_max_str_digits() return (f'<{x.__class__.__name__} instance with roughly {k} ' f'digits (limit at {max_digits}) at 0x{id(x):x}>') diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py index 7c4fa4b1c57e8f..d5631efcdb75b7 100644 --- a/Lib/test/test_reprlib.py +++ b/Lib/test/test_reprlib.py @@ -179,7 +179,7 @@ def re_msg(n, d): self.assertRegex(r(n), re_msg(n, k + 1)) with self.subTest(f'10 ** {k} - 1', k=k): n = 10 ** k - 1 - # For k >> 1, since math.log10(n) == math.log10(n-1), + # Here, since math.log10(n) == math.log10(n-1), # the number of digits of n - 1 is overestimated. self.assertRaises(ValueError, repr, n) self.assertRegex(r(n), re_msg(n, k + 1))