From b07b8eab3429a802f44ea7ebf09f614c6efc9642 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 9 Oct 2016 23:20:52 +0200 Subject: [PATCH 1/6] A real fix for #250 --- python2/typing.py | 19 ++++++++++++------- src/typing.py | 15 ++++++++++----- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/python2/typing.py b/python2/typing.py index cb5b3edd9..d19031778 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1046,12 +1046,6 @@ class GenericMeta(TypingMeta, abc.ABCMeta): def __new__(cls, name, bases, namespace, tvars=None, args=None, origin=None, extra=None): - if extra is None: - extra = namespace.get('__extra__') - if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: - bases = (extra,) + bases - self = super(GenericMeta, cls).__new__(cls, name, bases, namespace) - if tvars is not None: # Called from __getitem__() below. assert origin is not None @@ -1092,6 +1086,17 @@ def __new__(cls, name, bases, namespace, ", ".join(str(g) for g in gvars))) tvars = gvars + if extra is None: + extra = namespace.get('__extra__') + if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: + bases = (extra,) + bases + bases = tuple(b.__origin__ if hasattr(b, '__origin__') + and b.__origin__ is not None else b for b in bases) + if any(isinstance(b, GenericMeta) and b is not Generic + for b in bases): + bases = tuple(b for b in bases if b is not Generic) + self = super(GenericMeta, cls).__new__(cls, name, bases, namespace) + self.__parameters__ = tvars self.__args__ = args self.__origin__ = origin @@ -1180,7 +1185,7 @@ def __getitem__(self, params): tvars = _type_vars(params) args = params return self.__class__(self.__name__, - (self,) + self.__bases__, + self.__bases__, dict(self.__dict__), tvars=tvars, args=args, diff --git a/src/typing.py b/src/typing.py index 35d562e0b..6dfdcccb5 100644 --- a/src/typing.py +++ b/src/typing.py @@ -939,10 +939,6 @@ class GenericMeta(TypingMeta, abc.ABCMeta): def __new__(cls, name, bases, namespace, tvars=None, args=None, origin=None, extra=None): - if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: - bases = (extra,) + bases - self = super().__new__(cls, name, bases, namespace, _root=True) - if tvars is not None: # Called from __getitem__() below. assert origin is not None @@ -983,6 +979,15 @@ def __new__(cls, name, bases, namespace, ", ".join(str(g) for g in gvars))) tvars = gvars + if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: + bases = (extra,) + bases + bases = tuple(b.__origin__ if hasattr(b, '__origin__') + and b.__origin__ is not None else b for b in bases) + if any(isinstance(b, GenericMeta) and b is not Generic + for b in bases): + bases = tuple(b for b in bases if b is not Generic) + self = super().__new__(cls, name, bases, namespace, _root=True) + self.__parameters__ = tvars self.__args__ = args self.__origin__ = origin @@ -1071,7 +1076,7 @@ def __getitem__(self, params): tvars = _type_vars(params) args = params return self.__class__(self.__name__, - (self,) + self.__bases__, + self.__bases__, dict(self.__dict__), tvars=tvars, args=args, From eb7801bb3d96643ffa0f943cb6f685795eb27946 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 9 Oct 2016 23:37:25 +0200 Subject: [PATCH 2/6] Use _gorg for the case of multiply parameterized generics --- python2/typing.py | 3 +-- src/typing.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/python2/typing.py b/python2/typing.py index d19031778..f4374ac0d 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1090,8 +1090,7 @@ def __new__(cls, name, bases, namespace, extra = namespace.get('__extra__') if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: bases = (extra,) + bases - bases = tuple(b.__origin__ if hasattr(b, '__origin__') - and b.__origin__ is not None else b for b in bases) + bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b for b in bases) if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): bases = tuple(b for b in bases if b is not Generic) diff --git a/src/typing.py b/src/typing.py index 6dfdcccb5..1ab8d0d57 100644 --- a/src/typing.py +++ b/src/typing.py @@ -981,8 +981,7 @@ def __new__(cls, name, bases, namespace, if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: bases = (extra,) + bases - bases = tuple(b.__origin__ if hasattr(b, '__origin__') - and b.__origin__ is not None else b for b in bases) + bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b for b in bases) if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): bases = tuple(b for b in bases if b is not Generic) From be5d7ed51aebe7ad5e65f985ffe835c868a7611c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 9 Oct 2016 23:40:04 +0200 Subject: [PATCH 3/6] Formatting --- python2/typing.py | 3 +-- src/typing.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/python2/typing.py b/python2/typing.py index f4374ac0d..490167cd4 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1091,8 +1091,7 @@ def __new__(cls, name, bases, namespace, if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: bases = (extra,) + bases bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b for b in bases) - if any(isinstance(b, GenericMeta) and b is not Generic - for b in bases): + if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): bases = tuple(b for b in bases if b is not Generic) self = super(GenericMeta, cls).__new__(cls, name, bases, namespace) diff --git a/src/typing.py b/src/typing.py index 1ab8d0d57..c4f0fdc8e 100644 --- a/src/typing.py +++ b/src/typing.py @@ -982,8 +982,7 @@ def __new__(cls, name, bases, namespace, if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: bases = (extra,) + bases bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b for b in bases) - if any(isinstance(b, GenericMeta) and b is not Generic - for b in bases): + if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): bases = tuple(b for b in bases if b is not Generic) self = super().__new__(cls, name, bases, namespace, _root=True) From 63482faf6775d68f6de073a3a2d7e506949e809e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 11 Oct 2016 00:16:02 +0200 Subject: [PATCH 4/6] Response to comments + some tests --- python2/test_typing.py | 13 +++++++++++++ python2/typing.py | 6 ++++++ src/test_typing.py | 13 +++++++++++++ src/typing.py | 6 ++++++ 4 files changed, 38 insertions(+) diff --git a/python2/test_typing.py b/python2/test_typing.py index 8dd1acb06..23d26b0ef 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -598,6 +598,19 @@ class MM1(MutableMapping[str, str], collections_abc.MutableMapping): class MM2(collections_abc.MutableMapping, MutableMapping[str, str]): pass + def test_orig_bases(self): + T = TypeVar('T') + class C(typing.Dict[str, T]): pass + self.assertEqual(C.__orig_bases__, (typing.Dict[str, T],)) + + def test_multi_subscr_base(self): + T = TypeVar('T') + U = TypeVar('U') + V = TypeVar('V') + # these should just work + class C(List[T][U][V]): pass + class D(C, List[T][U][V]): pass + def test_pickle(self): global C # pickle wants to reference the class by name T = TypeVar('T') diff --git a/python2/typing.py b/python2/typing.py index 490167cd4..2c373c5f2 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1086,11 +1086,14 @@ def __new__(cls, name, bases, namespace, ", ".join(str(g) for g in gvars))) tvars = gvars + orig_bases = bases if extra is None: extra = namespace.get('__extra__') if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: bases = (extra,) + bases bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b for b in bases) + + # remove bare Generic from bases if there are other generic bases if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): bases = tuple(b for b in bases if b is not Generic) self = super(GenericMeta, cls).__new__(cls, name, bases, namespace) @@ -1101,6 +1104,8 @@ def __new__(cls, name, bases, namespace, self.__extra__ = extra # Speed hack (https://github.com/python/typing/issues/196). self.__next_in_mro__ = _next_in_mro(self) + if origin is None: + self.__orig_bases__ = orig_bases # This allows unparameterized generic collections to be used # with issubclass() and isinstance() in the same way as their @@ -1405,6 +1410,7 @@ def _get_protocol_attrs(self): attr != '__next_in_mro__' and attr != '__parameters__' and attr != '__origin__' and + attr != '__orig_bases__' and attr != '__extra__' and attr != '__module__'): attrs.add(attr) diff --git a/src/test_typing.py b/src/test_typing.py index dff737ae1..298b70e34 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -625,6 +625,19 @@ class MM1(MutableMapping[str, str], collections_abc.MutableMapping): class MM2(collections_abc.MutableMapping, MutableMapping[str, str]): pass + def test_orig_bases(self): + T = TypeVar('T') + class C(typing.Dict[str, T]): ... + self.assertEqual(C.__orig_bases__, (typing.Dict[str, T],)) + + def test_multi_subscr_base(self): + T = TypeVar('T') + U = TypeVar('U') + V = TypeVar('V') + # these should just work + class C(List[T][U][V]): ... + class D(C, List[T][U][V]): ... + def test_pickle(self): global C # pickle wants to reference the class by name T = TypeVar('T') diff --git a/src/typing.py b/src/typing.py index c4f0fdc8e..8c38cd1a3 100644 --- a/src/typing.py +++ b/src/typing.py @@ -979,9 +979,12 @@ def __new__(cls, name, bases, namespace, ", ".join(str(g) for g in gvars))) tvars = gvars + orig_bases = bases if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: bases = (extra,) + bases bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b for b in bases) + + # remove bare Generic from bases if there are other generic bases if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): bases = tuple(b for b in bases if b is not Generic) self = super().__new__(cls, name, bases, namespace, _root=True) @@ -992,6 +995,8 @@ def __new__(cls, name, bases, namespace, self.__extra__ = extra # Speed hack (https://github.com/python/typing/issues/196). self.__next_in_mro__ = _next_in_mro(self) + if origin is None: + self.__orig_bases__ = orig_bases # This allows unparameterized generic collections to be used # with issubclass() and isinstance() in the same way as their @@ -1488,6 +1493,7 @@ def _get_protocol_attrs(self): attr != '__next_in_mro__' and attr != '__parameters__' and attr != '__origin__' and + attr != '__orig_bases__' and attr != '__extra__' and attr != '__module__'): attrs.add(attr) From 307bf649e54a72842f59dfa6b3b62aa49c28450d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Oct 2016 17:59:48 +0200 Subject: [PATCH 5/6] Add tests illustrating new dunder attributes; preserve initial class for generic instances --- python2/test_typing.py | 43 ++++++++++++++++++++++++++++++++++++++++- python2/typing.py | 14 +++++++++----- src/test_typing.py | 44 +++++++++++++++++++++++++++++++++++++++++- src/typing.py | 14 +++++++++----- 4 files changed, 103 insertions(+), 12 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index 23d26b0ef..a8db2e8b4 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -603,13 +603,54 @@ def test_orig_bases(self): class C(typing.Dict[str, T]): pass self.assertEqual(C.__orig_bases__, (typing.Dict[str, T],)) + def test_naive_runtime_checks(self): + def naive_dict_check(obj, tp): + # Check if a dictionary conforms to Dict type + if len(tp.__parameters__) > 0: + return NotImplemented + if tp.__args__: + KT, VT = tp.__args__ + return all(isinstance(k, KT) and isinstance(v, VT) + for k, v in obj.items()) + self.assertTrue(naive_dict_check({'x': 1}, typing.Dict[typing.Text, int])) + self.assertFalse(naive_dict_check({1: 'x'}, typing.Dict[typing.Text, int])) + self.assertIs(naive_dict_check({1: 'x'}, typing.Dict[typing.Text, T]), NotImplemented) + + def naive_generic_check(obj, tp): + # Check if an instance conforms to the generic class + if not hasattr(obj, '__orig_class__'): + return NotImplemented + return obj.__orig_class__ == tp + class Node(Generic[T]): pass + self.assertTrue(naive_generic_check(Node[int](), Node[int])) + self.assertFalse(naive_generic_check(Node[str](), Node[int])) + self.assertFalse(naive_generic_check(Node[str](), List)) + self.assertIs(naive_generic_check([1,2,3], Node[int]), NotImplemented) + + def naive_list_base_check(obj, tp): + # Check if list conforms to a List subclass + return all(isinstance(x, tp.__orig_bases__[0].__args__[0]) + for x in obj) + class C(List[int]): pass + self.assertTrue(naive_list_base_check([1, 2, 3], C)) + self.assertFalse(naive_list_base_check(['a', 'b'], C)) + def test_multi_subscr_base(self): T = TypeVar('T') U = TypeVar('U') V = TypeVar('V') - # these should just work class C(List[T][U][V]): pass class D(C, List[T][U][V]): pass + self.assertEqual(C.__parameters__, (V,)) + self.assertEqual(D.__parameters__, (V,)) + self.assertEqual(C[int].__parameters__, ()) + self.assertEqual(D[int].__parameters__, ()) + self.assertEqual(C[int].__args__, (int,)) + self.assertEqual(D[int].__args__, (int,)) + self.assertEqual(C.__bases__, (List,)) + self.assertEqual(D.__bases__, (C, List)) + self.assertEqual(C.__orig_bases__, (List[T][U][V],)) + self.assertEqual(D.__orig_bases__, (C, List[T][U][V])) def test_pickle(self): global C # pickle wants to reference the class by name diff --git a/python2/typing.py b/python2/typing.py index 2c373c5f2..e1678264f 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1045,7 +1045,7 @@ class GenericMeta(TypingMeta, abc.ABCMeta): """Metaclass for generic types.""" def __new__(cls, name, bases, namespace, - tvars=None, args=None, origin=None, extra=None): + tvars=None, args=None, origin=None, extra=None, orig_bases=None): if tvars is not None: # Called from __getitem__() below. assert origin is not None @@ -1086,7 +1086,7 @@ def __new__(cls, name, bases, namespace, ", ".join(str(g) for g in gvars))) tvars = gvars - orig_bases = bases + initial_bases = bases if extra is None: extra = namespace.get('__extra__') if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: @@ -1104,8 +1104,9 @@ def __new__(cls, name, bases, namespace, self.__extra__ = extra # Speed hack (https://github.com/python/typing/issues/196). self.__next_in_mro__ = _next_in_mro(self) - if origin is None: - self.__orig_bases__ = orig_bases + # Preserve base classes on subclassing (__bases__ are type erased now). + if orig_bases is None: + self.__orig_bases__ = initial_bases # This allows unparameterized generic collections to be used # with issubclass() and isinstance() in the same way as their @@ -1193,7 +1194,8 @@ def __getitem__(self, params): tvars=tvars, args=args, origin=self, - extra=self.__extra__) + extra=self.__extra__, + orig_bases=self.__orig_bases__) def __instancecheck__(self, instance): # Since we extend ABC.__subclasscheck__ and @@ -1240,6 +1242,8 @@ def __new__(cls, *args, **kwds): else: origin = _gorg(cls) obj = cls.__next_in_mro__.__new__(origin) + if '__dict__' in cls.__dict__: + obj.__orig_class__ = cls obj.__init__(*args, **kwds) return obj diff --git a/src/test_typing.py b/src/test_typing.py index 298b70e34..0419e8a44 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -630,13 +630,55 @@ def test_orig_bases(self): class C(typing.Dict[str, T]): ... self.assertEqual(C.__orig_bases__, (typing.Dict[str, T],)) + def test_naive_runtime_checks(self): + def naive_dict_check(obj, tp): + # Check if a dictionary conforms to Dict type + if len(tp.__parameters__) > 0: + return NotImplemented + if tp.__args__: + KT, VT = tp.__args__ + return all(isinstance(k, KT) and isinstance(v, VT) + for k, v in obj.items()) + self.assertTrue(naive_dict_check({'x': 1}, typing.Dict[str, int])) + self.assertFalse(naive_dict_check({1: 'x'}, typing.Dict[str, int])) + self.assertIs(naive_dict_check({1: 'x'}, typing.Dict[str, T]), NotImplemented) + + def naive_generic_check(obj, tp): + # Check if an instance conforms to the generic class + if not hasattr(obj, '__orig_class__'): + return NotImplemented + return obj.__orig_class__ == tp + class Node(Generic[T]): ... + self.assertTrue(naive_generic_check(Node[int](), Node[int])) + self.assertFalse(naive_generic_check(Node[str](), Node[int])) + self.assertFalse(naive_generic_check(Node[str](), List)) + self.assertIs(naive_generic_check([1,2,3], Node[int]), NotImplemented) + + def naive_list_base_check(obj, tp): + # Check if list conforms to a List subclass + return all(isinstance(x, tp.__orig_bases__[0].__args__[0]) + for x in obj) + class C(List[int]): ... + self.assertTrue(naive_list_base_check([1, 2, 3], C)) + self.assertFalse(naive_list_base_check(['a', 'b'], C)) + def test_multi_subscr_base(self): T = TypeVar('T') U = TypeVar('U') V = TypeVar('V') - # these should just work class C(List[T][U][V]): ... class D(C, List[T][U][V]): ... + self.assertEqual(C.__parameters__, (V,)) + self.assertEqual(D.__parameters__, (V,)) + self.assertEqual(C[int].__parameters__, ()) + self.assertEqual(D[int].__parameters__, ()) + self.assertEqual(C[int].__args__, (int,)) + self.assertEqual(D[int].__args__, (int,)) + self.assertEqual(C.__bases__, (List,)) + self.assertEqual(D.__bases__, (C, List)) + self.assertEqual(C.__orig_bases__, (List[T][U][V],)) + self.assertEqual(D.__orig_bases__, (C, List[T][U][V])) + def test_pickle(self): global C # pickle wants to reference the class by name diff --git a/src/typing.py b/src/typing.py index 8c38cd1a3..5ed3aaec3 100644 --- a/src/typing.py +++ b/src/typing.py @@ -938,7 +938,7 @@ class GenericMeta(TypingMeta, abc.ABCMeta): """Metaclass for generic types.""" def __new__(cls, name, bases, namespace, - tvars=None, args=None, origin=None, extra=None): + tvars=None, args=None, origin=None, extra=None, orig_bases=None): if tvars is not None: # Called from __getitem__() below. assert origin is not None @@ -979,7 +979,7 @@ def __new__(cls, name, bases, namespace, ", ".join(str(g) for g in gvars))) tvars = gvars - orig_bases = bases + initial_bases = bases if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: bases = (extra,) + bases bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b for b in bases) @@ -995,8 +995,9 @@ def __new__(cls, name, bases, namespace, self.__extra__ = extra # Speed hack (https://github.com/python/typing/issues/196). self.__next_in_mro__ = _next_in_mro(self) - if origin is None: - self.__orig_bases__ = orig_bases + # Preserve base classes on subclassing (__bases__ are type erased now). + if orig_bases is None: + self.__orig_bases__ = initial_bases # This allows unparameterized generic collections to be used # with issubclass() and isinstance() in the same way as their @@ -1084,7 +1085,8 @@ def __getitem__(self, params): tvars=tvars, args=args, origin=self, - extra=self.__extra__) + extra=self.__extra__, + orig_bases=self.__orig_bases__) def __instancecheck__(self, instance): # Since we extend ABC.__subclasscheck__ and @@ -1128,6 +1130,8 @@ def __new__(cls, *args, **kwds): else: origin = _gorg(cls) obj = cls.__next_in_mro__.__new__(origin) + if '__dict__' in cls.__dict__: + obj.__orig_class__ = cls obj.__init__(*args, **kwds) return obj From f4932bb33887448615bcb27098341b74786b0b4d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 21 Oct 2016 01:14:43 +0200 Subject: [PATCH 6/6] Response to comments --- python2/test_typing.py | 10 ++++++---- python2/typing.py | 4 +++- src/test_typing.py | 10 ++++++---- src/typing.py | 4 +++- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index a8db2e8b4..dca9a4f54 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -607,25 +607,27 @@ def test_naive_runtime_checks(self): def naive_dict_check(obj, tp): # Check if a dictionary conforms to Dict type if len(tp.__parameters__) > 0: - return NotImplemented + raise NotImplementedError if tp.__args__: KT, VT = tp.__args__ return all(isinstance(k, KT) and isinstance(v, VT) for k, v in obj.items()) self.assertTrue(naive_dict_check({'x': 1}, typing.Dict[typing.Text, int])) self.assertFalse(naive_dict_check({1: 'x'}, typing.Dict[typing.Text, int])) - self.assertIs(naive_dict_check({1: 'x'}, typing.Dict[typing.Text, T]), NotImplemented) + with self.assertRaises(NotImplementedError): + naive_dict_check({1: 'x'}, typing.Dict[typing.Text, T]) def naive_generic_check(obj, tp): # Check if an instance conforms to the generic class if not hasattr(obj, '__orig_class__'): - return NotImplemented + raise NotImplementedError return obj.__orig_class__ == tp class Node(Generic[T]): pass self.assertTrue(naive_generic_check(Node[int](), Node[int])) self.assertFalse(naive_generic_check(Node[str](), Node[int])) self.assertFalse(naive_generic_check(Node[str](), List)) - self.assertIs(naive_generic_check([1,2,3], Node[int]), NotImplemented) + with self.assertRaises(NotImplementedError): + naive_generic_check([1,2,3], Node[int]) def naive_list_base_check(obj, tp): # Check if list conforms to a List subclass diff --git a/python2/typing.py b/python2/typing.py index e1678264f..0bec764a4 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1242,8 +1242,10 @@ def __new__(cls, *args, **kwds): else: origin = _gorg(cls) obj = cls.__next_in_mro__.__new__(origin) - if '__dict__' in cls.__dict__: + try: obj.__orig_class__ = cls + except AttributeError: + pass obj.__init__(*args, **kwds) return obj diff --git a/src/test_typing.py b/src/test_typing.py index 0419e8a44..bf7053b9e 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -634,25 +634,27 @@ def test_naive_runtime_checks(self): def naive_dict_check(obj, tp): # Check if a dictionary conforms to Dict type if len(tp.__parameters__) > 0: - return NotImplemented + raise NotImplementedError if tp.__args__: KT, VT = tp.__args__ return all(isinstance(k, KT) and isinstance(v, VT) for k, v in obj.items()) self.assertTrue(naive_dict_check({'x': 1}, typing.Dict[str, int])) self.assertFalse(naive_dict_check({1: 'x'}, typing.Dict[str, int])) - self.assertIs(naive_dict_check({1: 'x'}, typing.Dict[str, T]), NotImplemented) + with self.assertRaises(NotImplementedError): + naive_dict_check({1: 'x'}, typing.Dict[str, T]) def naive_generic_check(obj, tp): # Check if an instance conforms to the generic class if not hasattr(obj, '__orig_class__'): - return NotImplemented + raise NotImplementedError return obj.__orig_class__ == tp class Node(Generic[T]): ... self.assertTrue(naive_generic_check(Node[int](), Node[int])) self.assertFalse(naive_generic_check(Node[str](), Node[int])) self.assertFalse(naive_generic_check(Node[str](), List)) - self.assertIs(naive_generic_check([1,2,3], Node[int]), NotImplemented) + with self.assertRaises(NotImplementedError): + naive_generic_check([1,2,3], Node[int]) def naive_list_base_check(obj, tp): # Check if list conforms to a List subclass diff --git a/src/typing.py b/src/typing.py index 5ed3aaec3..930ba0c6a 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1130,8 +1130,10 @@ def __new__(cls, *args, **kwds): else: origin = _gorg(cls) obj = cls.__next_in_mro__.__new__(origin) - if '__dict__' in cls.__dict__: + try: obj.__orig_class__ = cls + except AttributeError: + pass obj.__init__(*args, **kwds) return obj