Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
19 changes: 17 additions & 2 deletions Lib/reprlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def wrapper(self):
wrapper.__doc__ = getattr(user_function, '__doc__')
wrapper.__name__ = getattr(user_function, '__name__')
wrapper.__qualname__ = getattr(user_function, '__qualname__')
wrapper.__annotations__ = getattr(user_function, '__annotations__', {})
wrapper.__annotate__ = getattr(user_function, '__annotate__', None)
wrapper.__type_params__ = getattr(user_function, '__type_params__', ())
wrapper.__wrapped__ = user_function
return wrapper
Expand Down Expand Up @@ -181,7 +181,22 @@ 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)))
# 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}>')
if len(s) > self.maxlong:
i = max(0, (self.maxlong-3)//2)
j = max(0, self.maxlong-3-i)
Expand Down
107 changes: 72 additions & 35 deletions Lib/test/test_reprlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Nick Mathewson
"""

import annotationlib
import sys
import os
import shutil
Expand All @@ -11,7 +12,7 @@
import unittest
import textwrap

from test.support import verbose
from test.support import verbose, EqualToForwardRef
from test.support.os_helper import create_empty_file
from reprlib import repr as r # Don't shadow builtin repr
from reprlib import Repr
Expand Down Expand Up @@ -149,15 +150,40 @@ def test_frozenset(self):
eq(r(frozenset({1, 2, 3, 4, 5, 6})), "frozenset({1, 2, 3, 4, 5, 6})")
eq(r(frozenset({1, 2, 3, 4, 5, 6, 7})), "frozenset({1, 2, 3, 4, 5, 6, ...})")

@unittest.expectedFailure # TODO: RUSTPYTHON
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
# 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))

def test_instance(self):
eq = self.assertEqual
Expand All @@ -172,24 +198,23 @@ def test_instance(self):
eq(r(i3), ("<ClassWithFailingRepr instance at %#x>"%id(i3)))

s = r(ClassWithFailingRepr)
self.assertTrue(s.startswith("<class "))
self.assertTrue(s.endswith(">"))
self.assertStartsWith(s, "<class ")
self.assertEndsWith(s, ">")
self.assertIn(s.find("..."), [12, 13])

def test_lambda(self):
r = repr(lambda x: x)
self.assertTrue(r.startswith("<function ReprTests.test_lambda.<locals>.<lambda"), r)
self.assertStartsWith(r, "<function ReprTests.test_lambda.<locals>.<lambda")
# XXX anonymous functions? see func_repr

# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_builtin_function(self):
eq = self.assertEqual
# Functions
eq(repr(hash), '<built-in function hash>')
# Methods
self.assertTrue(repr(''.split).startswith(
'<built-in method split of str object at 0x'))
self.assertStartsWith(repr(''.split),
'<built-in method split of str object at 0x')

def test_range(self):
eq = self.assertEqual
Expand All @@ -214,8 +239,7 @@ def test_nesting(self):
eq(r([[[[[[{}]]]]]]), "[[[[[[{}]]]]]]")
eq(r([[[[[[[{}]]]]]]]), "[[[[[[[...]]]]]]]")

# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_cell(self):
def get_cell():
x = 42
Expand Down Expand Up @@ -376,20 +400,20 @@ def test_valid_indent(self):
'object': {
1: 'two',
b'three': [
(4.5, 6.7),
(4.5, 6.25),
[set((8, 9)), frozenset((10, 11))],
],
},
'tests': (
(dict(indent=None), '''\
{1: 'two', b'three': [(4.5, 6.7), [{8, 9}, frozenset({10, 11})]]}'''),
{1: 'two', b'three': [(4.5, 6.25), [{8, 9}, frozenset({10, 11})]]}'''),
(dict(indent=False), '''\
{
1: 'two',
b'three': [
(
4.5,
6.7,
6.25,
),
[
{
Expand All @@ -409,7 +433,7 @@ def test_valid_indent(self):
b'three': [
(
4.5,
6.7,
6.25,
),
[
{
Expand All @@ -429,7 +453,7 @@ def test_valid_indent(self):
b'three': [
(
4.5,
6.7,
6.25,
),
[
{
Expand All @@ -449,7 +473,7 @@ def test_valid_indent(self):
b'three': [
(
4.5,
6.7,
6.25,
),
[
{
Expand All @@ -469,7 +493,7 @@ def test_valid_indent(self):
b'three': [
(
4.5,
6.7,
6.25,
),
[
{
Expand Down Expand Up @@ -497,7 +521,7 @@ def test_valid_indent(self):
b'three': [
(
4.5,
6.7,
6.25,
),
[
{
Expand All @@ -517,7 +541,7 @@ def test_valid_indent(self):
-->b'three': [
-->-->(
-->-->-->4.5,
-->-->-->6.7,
-->-->-->6.25,
-->-->),
-->-->[
-->-->-->{
Expand All @@ -537,7 +561,7 @@ def test_valid_indent(self):
....b'three': [
........(
............4.5,
............6.7,
............6.25,
........),
........[
............{
Expand Down Expand Up @@ -733,8 +757,8 @@ class baz:
importlib.invalidate_caches()
from areallylongpackageandmodulenametotestreprtruncation.areallylongpackageandmodulenametotestreprtruncation import baz
ibaz = baz.baz()
self.assertTrue(repr(ibaz).startswith(
"<%s.baz object at 0x" % baz.__name__))
self.assertStartsWith(repr(ibaz),
"<%s.baz object at 0x" % baz.__name__)

def test_method(self):
self._check_path_limitations('qux')
Expand All @@ -747,13 +771,13 @@ def amethod(self): pass
from areallylongpackageandmodulenametotestreprtruncation.areallylongpackageandmodulenametotestreprtruncation import qux
# Unbound methods first
r = repr(qux.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.amethod)
self.assertTrue(r.startswith('<function aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.amethod'), r)
self.assertStartsWith(r, '<function aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.amethod')
# Bound method next
iqux = qux.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa()
r = repr(iqux.amethod)
self.assertTrue(r.startswith(
self.assertStartsWith(r,
'<bound method aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.amethod of <%s.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa object at 0x' \
% (qux.__name__,) ), r)
% (qux.__name__,) )

@unittest.skip('needs a built-in function with a really long name')
def test_builtin_function(self):
Expand Down Expand Up @@ -822,8 +846,7 @@ def __repr__(self):

self.assertIs(X.f, X.__repr__.__wrapped__)

# TODO: RUSTPYTHON: AttributeError: 'TypeVar' object has no attribute '__name__'
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'TypeVar' object has no attribute '__name__'
def test__type_params__(self):
class My:
@recursive_repr()
Expand All @@ -835,5 +858,19 @@ def __repr__[T: str](self, default: T = '') -> str:
self.assertEqual(type_params[0].__name__, 'T')
self.assertEqual(type_params[0].__bound__, str)

def test_annotations(self):
class My:
@recursive_repr()
def __repr__(self, default: undefined = ...):
return default

annotations = annotationlib.get_annotations(
My.__repr__, format=annotationlib.Format.FORWARDREF
)
self.assertEqual(
annotations,
{'default': EqualToForwardRef("undefined", owner=My.__repr__)}
)

if __name__ == "__main__":
unittest.main()
Loading