Skip to content

Commit 8988cd9

Browse files
ilevkivskyigvanrossum
authored andcommitted
Alternative (simpler) repr for generics (#296)
1 parent 4425da9 commit 8988cd9

File tree

4 files changed

+182
-52
lines changed

4 files changed

+182
-52
lines changed

python2/test_typing.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -521,9 +521,9 @@ def test_init(self):
521521

522522
def test_repr(self):
523523
self.assertEqual(repr(SimpleMapping),
524-
__name__ + '.' + 'SimpleMapping<~XK, ~XV>')
524+
__name__ + '.' + 'SimpleMapping')
525525
self.assertEqual(repr(MySimpleMapping),
526-
__name__ + '.' + 'MySimpleMapping<~XK, ~XV>')
526+
__name__ + '.' + 'MySimpleMapping')
527527

528528
def test_chain_repr(self):
529529
T = TypeVar('T')
@@ -547,7 +547,36 @@ class C(Generic[T]):
547547
self.assertNotEqual(Z, Y[T])
548548

549549
self.assertTrue(str(Z).endswith(
550-
'.C<~T>[typing.Tuple[~S, ~T]]<~S, ~T>[~T, int]<~T>[str]'))
550+
'.C[typing.Tuple[str, int]]'))
551+
552+
def test_new_repr(self):
553+
T = TypeVar('T')
554+
U = TypeVar('U', covariant=True)
555+
S = TypeVar('S')
556+
557+
self.assertEqual(repr(List), 'typing.List')
558+
self.assertEqual(repr(List[T]), 'typing.List[~T]')
559+
self.assertEqual(repr(List[U]), 'typing.List[+U]')
560+
self.assertEqual(repr(List[S][T][int]), 'typing.List[int]')
561+
self.assertEqual(repr(List[int]), 'typing.List[int]')
562+
563+
def test_new_repr_complex(self):
564+
T = TypeVar('T')
565+
TS = TypeVar('TS')
566+
567+
self.assertEqual(repr(typing.Mapping[T, TS][TS, T]), 'typing.Mapping[~TS, ~T]')
568+
self.assertEqual(repr(List[Tuple[T, TS]][int, T]),
569+
'typing.List[typing.Tuple[int, ~T]]')
570+
self.assertEqual(repr(List[Tuple[T, T]][List[int]]),
571+
'typing.List[typing.Tuple[typing.List[int], typing.List[int]]]')
572+
573+
def test_new_repr_bare(self):
574+
T = TypeVar('T')
575+
self.assertEqual(repr(Generic[T]), 'typing.Generic[~T]')
576+
self.assertEqual(repr(typing._Protocol[T]), 'typing.Protocol[~T]')
577+
class C(typing.Dict[Any, Any]): pass
578+
# this line should just work
579+
repr(C.__mro__)
551580

552581
def test_dict(self):
553582
T = TypeVar('T')
@@ -635,12 +664,12 @@ class C(Generic[T]):
635664
if not PY32:
636665
self.assertEqual(C.__qualname__,
637666
'GenericTests.test_repr_2.<locals>.C')
638-
self.assertEqual(repr(C).split('.')[-1], 'C<~T>')
667+
self.assertEqual(repr(C).split('.')[-1], 'C')
639668
X = C[int]
640669
self.assertEqual(X.__module__, __name__)
641670
if not PY32:
642671
self.assertEqual(X.__qualname__, 'C')
643-
self.assertEqual(repr(X).split('.')[-1], 'C<~T>[int]')
672+
self.assertEqual(repr(X).split('.')[-1], 'C[int]')
644673

645674
class Y(C[int]):
646675
pass

python2/typing.py

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,8 @@ def __getitem__(self, parameter):
276276
if not issubclass(parameter, self.type_var.__constraints__):
277277
raise TypeError("%s is not a valid substitution for %s." %
278278
(parameter, self.type_var))
279-
if isinstance(parameter, TypeVar):
280-
raise TypeError("%s cannot be re-parameterized." % self.type_var)
279+
if isinstance(parameter, TypeVar) and parameter is not self.type_var:
280+
raise TypeError("%s cannot be re-parameterized." % self)
281281
return self.__class__(self.name, parameter,
282282
self.impl_type, self.type_checker)
283283

@@ -398,12 +398,15 @@ def _eval_type(self, globalns, localns):
398398

399399
def _get_type_vars(self, tvars):
400400
if self.__type__:
401-
_get_type_vars(self.__type__, tvars)
401+
_get_type_vars([self.__type__], tvars)
402402

403403
def __repr__(self):
404+
return self._subs_repr([], [])
405+
406+
def _subs_repr(self, tvars, args):
404407
r = super(_ClassVar, self).__repr__()
405408
if self.__type__ is not None:
406-
r += '[{}]'.format(_type_repr(self.__type__))
409+
r += '[{}]'.format(_replace_arg(self.__type__, tvars, args))
407410
return r
408411

409412
def __hash__(self):
@@ -703,9 +706,12 @@ def _get_type_vars(self, tvars):
703706
_get_type_vars(self.__union_params__, tvars)
704707

705708
def __repr__(self):
709+
return self._subs_repr([], [])
710+
711+
def _subs_repr(self, tvars, args):
706712
r = super(_Union, self).__repr__()
707713
if self.__union_params__:
708-
r += '[%s]' % (', '.join(_type_repr(t)
714+
r += '[%s]' % (', '.join(_replace_arg(t, tvars, args)
709715
for t in self.__union_params__))
710716
return r
711717

@@ -805,9 +811,12 @@ def _eval_type(self, globalns, localns):
805811
return self.__class__(p, _root=True)
806812

807813
def __repr__(self):
814+
return self._subs_repr([], [])
815+
816+
def _subs_repr(self, tvars, args):
808817
r = super(_Tuple, self).__repr__()
809818
if self.__tuple_params__ is not None:
810-
params = [_type_repr(p) for p in self.__tuple_params__]
819+
params = [_replace_arg(p, tvars, args) for p in self.__tuple_params__]
811820
if self.__tuple_use_ellipsis__:
812821
params.append('...')
813822
if not params:
@@ -898,6 +907,8 @@ def __init__(self, args=None, result=None, _root=False):
898907
def _get_type_vars(self, tvars):
899908
if self.__args__ and self.__args__ is not Ellipsis:
900909
_get_type_vars(self.__args__, tvars)
910+
if self.__result__:
911+
_get_type_vars([self.__result__], tvars)
901912

902913
def _eval_type(self, globalns, localns):
903914
if self.__args__ is None and self.__result__ is None:
@@ -913,14 +924,17 @@ def _eval_type(self, globalns, localns):
913924
return self.__class__(args=args, result=result, _root=True)
914925

915926
def __repr__(self):
927+
return self._subs_repr([], [])
928+
929+
def _subs_repr(self, tvars, args):
916930
r = super(_Callable, self).__repr__()
917931
if self.__args__ is not None or self.__result__ is not None:
918932
if self.__args__ is Ellipsis:
919933
args_r = '...'
920934
else:
921-
args_r = '[%s]' % ', '.join(_type_repr(t)
935+
args_r = '[%s]' % ', '.join(_replace_arg(t, tvars, args)
922936
for t in self.__args__)
923-
r += '[%s, %s]' % (args_r, _type_repr(self.__result__))
937+
r += '[%s, %s]' % (args_r, _replace_arg(self.__result__, tvars, args))
924938
return r
925939

926940
def __getitem__(self, parameters):
@@ -985,6 +999,16 @@ def _geqv(a, b):
985999
return _gorg(a) is _gorg(b)
9861000

9871001

1002+
def _replace_arg(arg, tvars, args):
1003+
if hasattr(arg, '_subs_repr'):
1004+
return arg._subs_repr(tvars, args)
1005+
if isinstance(arg, TypeVar):
1006+
for i, tvar in enumerate(tvars):
1007+
if arg.__name__ == tvar.__name__:
1008+
return args[i]
1009+
return _type_repr(arg)
1010+
1011+
9881012
def _next_in_mro(cls):
9891013
"""Helper for Generic.__new__.
9901014
@@ -1115,17 +1139,29 @@ def _get_type_vars(self, tvars):
11151139
_get_type_vars(self.__parameters__, tvars)
11161140

11171141
def __repr__(self):
1118-
if self.__origin__ is not None:
1119-
r = repr(self.__origin__)
1120-
else:
1121-
r = super(GenericMeta, self).__repr__()
1122-
if self.__args__:
1123-
r += '[%s]' % (
1124-
', '.join(_type_repr(p) for p in self.__args__))
1125-
if self.__parameters__:
1126-
r += '<%s>' % (
1127-
', '.join(_type_repr(p) for p in self.__parameters__))
1128-
return r
1142+
if self.__origin__ is None:
1143+
return super(GenericMeta, self).__repr__()
1144+
return self._subs_repr([], [])
1145+
1146+
def _subs_repr(self, tvars, args):
1147+
assert len(tvars) == len(args)
1148+
# Construct the chain of __origin__'s.
1149+
current = self.__origin__
1150+
orig_chain = []
1151+
while current.__origin__ is not None:
1152+
orig_chain.append(current)
1153+
current = current.__origin__
1154+
# Replace type variables in __args__ if asked ...
1155+
str_args = []
1156+
for arg in self.__args__:
1157+
str_args.append(_replace_arg(arg, tvars, args))
1158+
# ... then continue replacing down the origin chain.
1159+
for cls in orig_chain:
1160+
new_str_args = []
1161+
for i, arg in enumerate(cls.__args__):
1162+
new_str_args.append(_replace_arg(arg, cls.__parameters__, str_args))
1163+
str_args = new_str_args
1164+
return super(GenericMeta, self).__repr__() + '[%s]' % ', '.join(str_args)
11291165

11301166
def __eq__(self, other):
11311167
if not isinstance(other, GenericMeta):
@@ -1158,11 +1194,11 @@ def __getitem__(self, params):
11581194
raise TypeError(
11591195
"Parameters to Generic[...] must all be unique")
11601196
tvars = params
1161-
args = None
1197+
args = params
11621198
elif self is _Protocol:
11631199
# _Protocol is internal, don't check anything.
11641200
tvars = params
1165-
args = None
1201+
args = params
11661202
elif self.__origin__ in (Generic, _Protocol):
11671203
# Can't subscript Generic[...] or _Protocol[...].
11681204
raise TypeError("Cannot subscript already-subscripted %s" %

src/test_typing.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -548,9 +548,9 @@ def test_init(self):
548548

549549
def test_repr(self):
550550
self.assertEqual(repr(SimpleMapping),
551-
__name__ + '.' + 'SimpleMapping<~XK, ~XV>')
551+
__name__ + '.' + 'SimpleMapping')
552552
self.assertEqual(repr(MySimpleMapping),
553-
__name__ + '.' + 'MySimpleMapping<~XK, ~XV>')
553+
__name__ + '.' + 'MySimpleMapping')
554554

555555
def test_chain_repr(self):
556556
T = TypeVar('T')
@@ -574,7 +574,36 @@ class C(Generic[T]):
574574
self.assertNotEqual(Z, Y[T])
575575

576576
self.assertTrue(str(Z).endswith(
577-
'.C<~T>[typing.Tuple[~S, ~T]]<~S, ~T>[~T, int]<~T>[str]'))
577+
'.C[typing.Tuple[str, int]]'))
578+
579+
def test_new_repr(self):
580+
T = TypeVar('T')
581+
U = TypeVar('U', covariant=True)
582+
S = TypeVar('S')
583+
584+
self.assertEqual(repr(List), 'typing.List')
585+
self.assertEqual(repr(List[T]), 'typing.List[~T]')
586+
self.assertEqual(repr(List[U]), 'typing.List[+U]')
587+
self.assertEqual(repr(List[S][T][int]), 'typing.List[int]')
588+
self.assertEqual(repr(List[int]), 'typing.List[int]')
589+
590+
def test_new_repr_complex(self):
591+
T = TypeVar('T')
592+
TS = TypeVar('TS')
593+
594+
self.assertEqual(repr(typing.Mapping[T, TS][TS, T]), 'typing.Mapping[~TS, ~T]')
595+
self.assertEqual(repr(List[Tuple[T, TS]][int, T]),
596+
'typing.List[typing.Tuple[int, ~T]]')
597+
self.assertEqual(repr(List[Tuple[T, T]][List[int]]),
598+
'typing.List[typing.Tuple[typing.List[int], typing.List[int]]]')
599+
600+
def test_new_repr_bare(self):
601+
T = TypeVar('T')
602+
self.assertEqual(repr(Generic[T]), 'typing.Generic[~T]')
603+
self.assertEqual(repr(typing._Protocol[T]), 'typing.Protocol[~T]')
604+
class C(typing.Dict[Any, Any]): ...
605+
# this line should just work
606+
repr(C.__mro__)
578607

579608
def test_dict(self):
580609
T = TypeVar('T')
@@ -662,12 +691,12 @@ class C(Generic[T]):
662691
if not PY32:
663692
self.assertEqual(C.__qualname__,
664693
'GenericTests.test_repr_2.<locals>.C')
665-
self.assertEqual(repr(C).split('.')[-1], 'C<~T>')
694+
self.assertEqual(repr(C).split('.')[-1], 'C')
666695
X = C[int]
667696
self.assertEqual(X.__module__, __name__)
668697
if not PY32:
669698
self.assertEqual(X.__qualname__, 'C')
670-
self.assertEqual(repr(X).split('.')[-1], 'C<~T>[int]')
699+
self.assertEqual(repr(X).split('.')[-1], 'C[int]')
671700

672701
class Y(C[int]):
673702
pass

0 commit comments

Comments
 (0)