From 61f0a0261f0fbfd0a6b981c370a8808aad00b107 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 29 Nov 2016 09:46:21 -0800 Subject: [PATCH] Issue #28790: Fix error when using Generic and __slots__ (Ivan L) --- Lib/test/test_typing.py | 38 ++++++++++++++++++++++++++++++++++++++ Lib/typing.py | 18 +++++++++++++++--- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 0910fd4022..d203ce3d59 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -896,6 +896,44 @@ class GenericTests(BaseTestCase): self.assertEqual(t, copy(t)) self.assertEqual(t, deepcopy(t)) + def test_parameterized_slots(self): + T = TypeVar('T') + class C(Generic[T]): + __slots__ = ('potato',) + + c = C() + c_int = C[int]() + self.assertEqual(C.__slots__, C[str].__slots__) + + c.potato = 0 + c_int.potato = 0 + with self.assertRaises(AttributeError): + c.tomato = 0 + with self.assertRaises(AttributeError): + c_int.tomato = 0 + + def foo(x: C['C']): ... + self.assertEqual(get_type_hints(foo, globals(), locals())['x'], C[C]) + self.assertEqual(get_type_hints(foo, globals(), locals())['x'].__slots__, + C.__slots__) + self.assertEqual(copy(C[int]), deepcopy(C[int])) + + def test_parameterized_slots_dict(self): + T = TypeVar('T') + class D(Generic[T]): + __slots__ = {'banana': 42} + + d = D() + d_int = D[int]() + self.assertEqual(D.__slots__, D[str].__slots__) + + d.banana = 'yes' + d_int.banana = 'yes' + with self.assertRaises(AttributeError): + d.foobar = 'no' + with self.assertRaises(AttributeError): + d_int.foobar = 'no' + def test_errors(self): with self.assertRaises(TypeError): B = SimpleMapping[XK, Any] diff --git a/Lib/typing.py b/Lib/typing.py index 1a943aca20..34845b747a 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -870,6 +870,17 @@ def _make_subclasshook(cls): return __extrahook__ +def _no_slots_copy(dct): + """Internal helper: copy class __dict__ and clean slots class variables. + (They will be re-created if necessary by normal class machinery.) + """ + dict_copy = dict(dct) + if '__slots__' in dict_copy: + for slot in dict_copy['__slots__']: + dict_copy.pop(slot, None) + return dict_copy + + class GenericMeta(TypingMeta, abc.ABCMeta): """Metaclass for generic types.""" @@ -967,7 +978,7 @@ class GenericMeta(TypingMeta, abc.ABCMeta): return self return self.__class__(self.__name__, self.__bases__, - dict(self.__dict__), + _no_slots_copy(self.__dict__), tvars=_type_vars(ev_args) if ev_args else None, args=ev_args, origin=ev_origin, @@ -1043,7 +1054,7 @@ class GenericMeta(TypingMeta, abc.ABCMeta): args = params return self.__class__(self.__name__, self.__bases__, - dict(self.__dict__), + _no_slots_copy(self.__dict__), tvars=tvars, args=args, origin=self, @@ -1059,7 +1070,8 @@ class GenericMeta(TypingMeta, abc.ABCMeta): return issubclass(instance.__class__, self) def __copy__(self): - return self.__class__(self.__name__, self.__bases__, dict(self.__dict__), + return self.__class__(self.__name__, self.__bases__, + _no_slots_copy(self.__dict__), self.__parameters__, self.__args__, self.__origin__, self.__extra__, self.__orig_bases__) -- 2.40.0