Skip to content

Commit 61f0a02

Browse files
committed
Issue python#28790: Fix error when using Generic and __slots__ (Ivan L)
1 parent 47a9a4b commit 61f0a02

File tree

2 files changed

+53
-3
lines changed

2 files changed

+53
-3
lines changed

Lib/test/test_typing.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,44 @@ class Node(Generic[T]): ...
896896
self.assertEqual(t, copy(t))
897897
self.assertEqual(t, deepcopy(t))
898898

899+
def test_parameterized_slots(self):
900+
T = TypeVar('T')
901+
class C(Generic[T]):
902+
__slots__ = ('potato',)
903+
904+
c = C()
905+
c_int = C[int]()
906+
self.assertEqual(C.__slots__, C[str].__slots__)
907+
908+
c.potato = 0
909+
c_int.potato = 0
910+
with self.assertRaises(AttributeError):
911+
c.tomato = 0
912+
with self.assertRaises(AttributeError):
913+
c_int.tomato = 0
914+
915+
def foo(x: C['C']): ...
916+
self.assertEqual(get_type_hints(foo, globals(), locals())['x'], C[C])
917+
self.assertEqual(get_type_hints(foo, globals(), locals())['x'].__slots__,
918+
C.__slots__)
919+
self.assertEqual(copy(C[int]), deepcopy(C[int]))
920+
921+
def test_parameterized_slots_dict(self):
922+
T = TypeVar('T')
923+
class D(Generic[T]):
924+
__slots__ = {'banana': 42}
925+
926+
d = D()
927+
d_int = D[int]()
928+
self.assertEqual(D.__slots__, D[str].__slots__)
929+
930+
d.banana = 'yes'
931+
d_int.banana = 'yes'
932+
with self.assertRaises(AttributeError):
933+
d.foobar = 'no'
934+
with self.assertRaises(AttributeError):
935+
d_int.foobar = 'no'
936+
899937
def test_errors(self):
900938
with self.assertRaises(TypeError):
901939
B = SimpleMapping[XK, Any]

Lib/typing.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,17 @@ def __extrahook__(subclass):
870870
return __extrahook__
871871

872872

873+
def _no_slots_copy(dct):
874+
"""Internal helper: copy class __dict__ and clean slots class variables.
875+
(They will be re-created if necessary by normal class machinery.)
876+
"""
877+
dict_copy = dict(dct)
878+
if '__slots__' in dict_copy:
879+
for slot in dict_copy['__slots__']:
880+
dict_copy.pop(slot, None)
881+
return dict_copy
882+
883+
873884
class GenericMeta(TypingMeta, abc.ABCMeta):
874885
"""Metaclass for generic types."""
875886

@@ -967,7 +978,7 @@ def _eval_type(self, globalns, localns):
967978
return self
968979
return self.__class__(self.__name__,
969980
self.__bases__,
970-
dict(self.__dict__),
981+
_no_slots_copy(self.__dict__),
971982
tvars=_type_vars(ev_args) if ev_args else None,
972983
args=ev_args,
973984
origin=ev_origin,
@@ -1043,7 +1054,7 @@ def __getitem__(self, params):
10431054
args = params
10441055
return self.__class__(self.__name__,
10451056
self.__bases__,
1046-
dict(self.__dict__),
1057+
_no_slots_copy(self.__dict__),
10471058
tvars=tvars,
10481059
args=args,
10491060
origin=self,
@@ -1059,7 +1070,8 @@ def __instancecheck__(self, instance):
10591070
return issubclass(instance.__class__, self)
10601071

10611072
def __copy__(self):
1062-
return self.__class__(self.__name__, self.__bases__, dict(self.__dict__),
1073+
return self.__class__(self.__name__, self.__bases__,
1074+
_no_slots_copy(self.__dict__),
10631075
self.__parameters__, self.__args__, self.__origin__,
10641076
self.__extra__, self.__orig_bases__)
10651077

0 commit comments

Comments
 (0)