Skip to content

Commit 8d84daa

Browse files
committed
feat(gh-145056): Support frozendict in reprlib
- Add Repr.repr_frozendict() sharing _repr_mapping() with repr_dict() - frozendict reprs were cut in the middle instead of abbreviated by item - Register frozendict in Repr._lookup so shadowed names are not matched
1 parent 82b612d commit 8d84daa

3 files changed

Lines changed: 85 additions & 5 deletions

File tree

‎Lib/reprlib.py‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class Repr:
4444
'frozenset': 'builtins',
4545
'deque': 'collections',
4646
'dict': 'builtins',
47+
'frozendict': 'builtins',
4748
'str': 'builtins',
4849
'int': 'builtins'
4950
}
@@ -153,12 +154,10 @@ def repr_frozenset(self, x, level):
153154
def repr_deque(self, x, level):
154155
return self._repr_iterable(x, level, 'deque([', '])', self.maxdeque)
155156

156-
def repr_dict(self, x, level):
157+
def _repr_mapping(self, x, level, left, right):
157158
n = len(x)
158-
if n == 0:
159-
return '{}'
160159
if level <= 0:
161-
return '{' + self.fillvalue + '}'
160+
return left + self.fillvalue + right
162161
newlevel = level - 1
163162
repr1 = self.repr1
164163
pieces = []
@@ -169,7 +168,17 @@ def repr_dict(self, x, level):
169168
if n > self.maxdict:
170169
pieces.append(self.fillvalue)
171170
s = self._join(pieces, level)
172-
return '{%s}' % (s,)
171+
return '%s%s%s' % (left, s, right)
172+
173+
def repr_dict(self, x, level):
174+
if not x:
175+
return '{}'
176+
return self._repr_mapping(x, level, '{', '}')
177+
178+
def repr_frozendict(self, x, level):
179+
if not x:
180+
return 'frozendict()'
181+
return self._repr_mapping(x, level, 'frozendict({', '})')
173182

174183
def repr_str(self, x, level):
175184
s = builtins.repr(x[:self.maxstring])

‎Lib/test/test_reprlib.py‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,14 @@ def test_container(self):
126126
d['arthur'] = 1
127127
eq(r(d), "{'alice': 1, 'arthur': 1, 'bob': 2, 'charles': 3, ...}")
128128

129+
# Frozendicts give up after 4 as well.
130+
eq(r(frozendict()), "frozendict()")
131+
d = frozendict(alice=1, bob=2, charles=3, dave=4)
132+
eq(r(d), "frozendict({'alice': 1, 'bob': 2, 'charles': 3, 'dave': 4})")
133+
d |= {'arthur': 1}
134+
eq(r(d),
135+
"frozendict({'alice': 1, 'arthur': 1, 'bob': 2, 'charles': 3, ...})")
136+
129137
# array.array after 5.
130138
eq(r(array('i')), "array('i')")
131139
eq(r(array('i', [1])), "array('i', [1])")
@@ -150,6 +158,25 @@ def test_frozenset(self):
150158
eq(r(frozenset({1, 2, 3, 4, 5, 6})), "frozenset({1, 2, 3, 4, 5, 6})")
151159
eq(r(frozenset({1, 2, 3, 4, 5, 6, 7})), "frozenset({1, 2, 3, 4, 5, 6, ...})")
152160

161+
def test_frozendict(self):
162+
eq = self.assertEqual
163+
eq(r(frozendict({1: 1})), "frozendict({1: 1})")
164+
eq(r(frozendict({1: 1, 2: 2, 3: 3})), "frozendict({1: 1, 2: 2, 3: 3})")
165+
eq(r(frozendict({1: 1, 2: 2, 3: 3, 4: 4})),
166+
"frozendict({1: 1, 2: 2, 3: 3, 4: 4})")
167+
eq(r(frozendict({1: 1, 2: 2, 3: 3, 4: 4, 5: 5})),
168+
"frozendict({1: 1, 2: 2, 3: 3, 4: 4, ...})")
169+
170+
# Keys are sorted and values are abbreviated, as for dict
171+
eq(r(frozendict({3: 'c', 1: 'a' * 40, 2: 'b'})),
172+
"frozendict({1: 'aaaaaaaaaaaa...aaaaaaaaaaaaa', 2: 'b', 3: 'c'})")
173+
174+
# The limit is shared with dict
175+
r2 = Repr(maxdict=2)
176+
eq(r2.repr(frozendict({1: 1, 2: 2})), "frozendict({1: 1, 2: 2})")
177+
eq(r2.repr(frozendict({1: 1, 2: 2, 3: 3})),
178+
"frozendict({1: 1, 2: 2, ...})")
179+
153180
def test_numbers(self):
154181
for x in [123, 1.0 / 3]:
155182
self.assertEqual(r(x), repr(x))
@@ -237,6 +264,12 @@ def test_nesting(self):
237264
eq(r([[[[[[{}]]]]]]), "[[[[[[{}]]]]]]")
238265
eq(r([[[[[[[{}]]]]]]]), "[[[[[[[...]]]]]]]")
239266

267+
r1 = Repr(maxlevel=1)
268+
eq(r1.repr(frozendict({1: frozendict()})),
269+
"frozendict({1: frozendict()})")
270+
eq(r1.repr(frozendict({1: frozendict({2: 3})})),
271+
"frozendict({1: frozendict({...})})")
272+
240273
def test_cell(self):
241274
def get_cell():
242275
x = 42
@@ -269,9 +302,11 @@ def test_unsortable(self):
269302
x = set([1j, 2j, 3j])
270303
y = frozenset(x)
271304
z = {1j: 1, 2j: 2}
305+
w = frozendict(z)
272306
r(x)
273307
r(y)
274308
r(z)
309+
r(w)
275310

276311
def test_valid_indent(self):
277312
test_cases = [
@@ -574,6 +609,32 @@ def test_valid_indent(self):
574609
}'''),
575610
),
576611
},
612+
{
613+
'object': frozendict({1: 'two', b'three': frozendict({4: 5})}),
614+
'tests': (
615+
(dict(indent=None), '''\
616+
frozendict({1: 'two', b'three': frozendict({4: 5})})'''),
617+
(dict(indent=4), '''\
618+
frozendict({
619+
1: 'two',
620+
b'three': frozendict({
621+
4: 5,
622+
}),
623+
})'''),
624+
(dict(indent=4, maxlevel=1), '''\
625+
frozendict({
626+
1: 'two',
627+
b'three': frozendict({...}),
628+
})'''),
629+
(dict(indent='-->'), '''\
630+
frozendict({
631+
-->1: 'two',
632+
-->b'three': frozendict({
633+
-->-->4: 5,
634+
-->}),
635+
})'''),
636+
),
637+
},
577638
]
578639
for test_case in test_cases:
579640
with self.subTest(test_object=test_case['object']):
@@ -622,6 +683,13 @@ def __repr__(self):
622683

623684
self.assertEqual(r(list()), "not builtins.list")
624685

686+
def test_shadowed_frozendict(self):
687+
class frozendict:
688+
def __repr__(self):
689+
return "not builtins.frozendict"
690+
691+
self.assertEqual(r(frozendict()), "not builtins.frozendict")
692+
625693
def test_custom_repr(self):
626694
class MyRepr(Repr):
627695

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:func:`reprlib.repr` and :class:`reprlib.Repr` now abbreviate
2+
:class:`frozendict` instances by item, like :class:`dict`, using the
3+
:attr:`~reprlib.Repr.maxdict` limit.

0 commit comments

Comments
 (0)