diff --git a/python2/test_typing.py b/python2/test_typing.py index 73a9f0d7..8dd1acb0 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -19,6 +19,7 @@ from typing import NamedTuple from typing import IO, TextIO, BinaryIO from typing import Pattern, Match +import abc import typing try: import collections.abc as collections_abc @@ -1059,6 +1060,8 @@ def __len__(self): return 0 self.assertEqual(len(MMC()), 0) + assert callable(MMC.update) + self.assertIsInstance(MMC(), typing.Mapping) class MMB(typing.MutableMapping[KT, VT]): def __getitem__(self, k): @@ -1083,6 +1086,81 @@ def __len__(self): self.assertIsSubclass(MMB, typing.Mapping) self.assertIsSubclass(MMC, typing.Mapping) + self.assertIsInstance(MMB[KT, VT](), typing.Mapping) + self.assertIsInstance(MMB[KT, VT](), collections.Mapping) + + self.assertIsSubclass(MMA, collections.Mapping) + self.assertIsSubclass(MMB, collections.Mapping) + self.assertIsSubclass(MMC, collections.Mapping) + + self.assertIsSubclass(MMB[str, str], typing.Mapping) + self.assertIsSubclass(MMC, MMA) + + class I(typing.Iterable): pass + self.assertNotIsSubclass(list, I) + + class G(typing.Generator[int, int, int]): pass + def g(): yield 0 + self.assertIsSubclass(G, typing.Generator) + self.assertIsSubclass(G, typing.Iterable) + if hasattr(collections, 'Generator'): + self.assertIsSubclass(G, collections.Generator) + self.assertIsSubclass(G, collections.Iterable) + self.assertNotIsSubclass(type(g), G) + + def test_subclassing_subclasshook(self): + + class Base(typing.Iterable): + @classmethod + def __subclasshook__(cls, other): + if other.__name__ == 'Foo': + return True + else: + return False + + class C(Base): pass + class Foo: pass + class Bar: pass + self.assertIsSubclass(Foo, Base) + self.assertIsSubclass(Foo, C) + self.assertNotIsSubclass(Bar, C) + + def test_subclassing_register(self): + + class A(typing.Container): pass + class B(A): pass + + class C: pass + A.register(C) + self.assertIsSubclass(C, A) + self.assertNotIsSubclass(C, B) + + class D: pass + B.register(D) + self.assertIsSubclass(D, A) + self.assertIsSubclass(D, B) + + class M(): pass + collections.MutableMapping.register(M) + self.assertIsSubclass(M, typing.Mapping) + + def test_collections_as_base(self): + + class M(collections.Mapping): pass + self.assertIsSubclass(M, typing.Mapping) + self.assertIsSubclass(M, typing.Iterable) + + class S(collections.MutableSequence): pass + self.assertIsSubclass(S, typing.MutableSequence) + self.assertIsSubclass(S, typing.Iterable) + + class I(collections.Iterable): pass + self.assertIsSubclass(I, typing.Iterable) + + class A(collections.Mapping): pass + class B: pass + A.register(B) + self.assertIsSubclass(B, typing.Mapping) class TypeTests(BaseTestCase): diff --git a/python2/typing.py b/python2/typing.py index 6cd51c6b..cb5b3edd 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1023,7 +1023,7 @@ def __extrahook__(cls, subclass): res = cls.__extra__.__subclasshook__(subclass) if res is not NotImplemented: return res - if cls.__extra__ in subclass.__mro__: + if cls.__extra__ in getattr(subclass, '__mro__', ()): return True for scls in cls.__extra__.__subclasses__(): if isinstance(scls, GenericMeta): @@ -1046,6 +1046,8 @@ 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) @@ -1093,14 +1095,17 @@ def __new__(cls, name, bases, namespace, self.__parameters__ = tvars self.__args__ = args self.__origin__ = origin - self.__extra__ = namespace.get('__extra__') + self.__extra__ = extra # Speed hack (https://github.com/python/typing/issues/196). self.__next_in_mro__ = _next_in_mro(self) # This allows unparameterized generic collections to be used # with issubclass() and isinstance() in the same way as their # collections.abc counterparts (e.g., isinstance([], Iterable)). - self.__subclasshook__ = _make_subclasshook(self) + if ('__subclasshook__' not in namespace and extra # allow overriding + or hasattr(self.__subclasshook__, '__name__') and + self.__subclasshook__.__name__ == '__extrahook__'): + self.__subclasshook__ = _make_subclasshook(self) if isinstance(extra, abc.ABCMeta): self._abc_registry = extra._abc_registry return self diff --git a/src/test_typing.py b/src/test_typing.py index cf3171f7..dff737ae 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -20,6 +20,7 @@ from typing import NamedTuple from typing import IO, TextIO, BinaryIO from typing import Pattern, Match +import abc import typing try: import collections.abc as collections_abc @@ -1385,6 +1386,8 @@ def __len__(self): return 0 self.assertEqual(len(MMC()), 0) + assert callable(MMC.update) + self.assertIsInstance(MMC(), typing.Mapping) class MMB(typing.MutableMapping[KT, VT]): def __getitem__(self, k): @@ -1409,6 +1412,82 @@ def __len__(self): self.assertIsSubclass(MMB, typing.Mapping) self.assertIsSubclass(MMC, typing.Mapping) + self.assertIsInstance(MMB[KT, VT](), typing.Mapping) + self.assertIsInstance(MMB[KT, VT](), collections.Mapping) + + self.assertIsSubclass(MMA, collections.Mapping) + self.assertIsSubclass(MMB, collections.Mapping) + self.assertIsSubclass(MMC, collections.Mapping) + + self.assertIsSubclass(MMB[str, str], typing.Mapping) + self.assertIsSubclass(MMC, MMA) + + class I(typing.Iterable): ... + self.assertNotIsSubclass(list, I) + + class G(typing.Generator[int, int, int]): ... + def g(): yield 0 + self.assertIsSubclass(G, typing.Generator) + self.assertIsSubclass(G, typing.Iterable) + if hasattr(collections, 'Generator'): + self.assertIsSubclass(G, collections.Generator) + self.assertIsSubclass(G, collections.Iterable) + self.assertNotIsSubclass(type(g), G) + + def test_subclassing_subclasshook(self): + + class Base(typing.Iterable): + @classmethod + def __subclasshook__(cls, other): + if other.__name__ == 'Foo': + return True + else: + return False + + class C(Base): ... + class Foo: ... + class Bar: ... + self.assertIsSubclass(Foo, Base) + self.assertIsSubclass(Foo, C) + self.assertNotIsSubclass(Bar, C) + + def test_subclassing_register(self): + + class A(typing.Container): ... + class B(A): ... + + class C: ... + A.register(C) + self.assertIsSubclass(C, A) + self.assertNotIsSubclass(C, B) + + class D: ... + B.register(D) + self.assertIsSubclass(D, A) + self.assertIsSubclass(D, B) + + class M(): ... + collections.MutableMapping.register(M) + self.assertIsSubclass(M, typing.Mapping) + + def test_collections_as_base(self): + + class M(collections.Mapping): ... + self.assertIsSubclass(M, typing.Mapping) + self.assertIsSubclass(M, typing.Iterable) + + class S(collections.MutableSequence): ... + self.assertIsSubclass(S, typing.MutableSequence) + self.assertIsSubclass(S, typing.Iterable) + + class I(collections.Iterable): ... + self.assertIsSubclass(I, typing.Iterable) + + class A(collections.Mapping, metaclass=abc.ABCMeta): ... + class B: ... + A.register(B) + self.assertIsSubclass(B, typing.Mapping) + class OtherABCTests(BaseTestCase): diff --git a/src/typing.py b/src/typing.py index 05b65b70..557fa589 100644 --- a/src/typing.py +++ b/src/typing.py @@ -993,7 +993,10 @@ def __new__(cls, name, bases, namespace, # This allows unparameterized generic collections to be used # with issubclass() and isinstance() in the same way as their # collections.abc counterparts (e.g., isinstance([], Iterable)). - self.__subclasshook__ = _make_subclasshook(self) + if ('__subclasshook__' not in namespace and extra # allow overriding + or hasattr(self.__subclasshook__, '__name__') and + self.__subclasshook__.__name__ == '__extrahook__'): + self.__subclasshook__ = _make_subclasshook(self) if isinstance(extra, abc.ABCMeta): self._abc_registry = extra._abc_registry return self