From 7baf6fe2a6d96ab09c807aac174650baa35d15dd Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 28 Sep 2016 00:10:31 +0200 Subject: [PATCH] Kill __subclasscheck__ (#283) This PR: - Fixes #136 - Fixes #133 - Partially addresses #203 (fixes the isinstance part, and multiple inheritance, still typing.Something is not a drop-in replacement for collections.abc.Something in terms of implementation). - Also fixes http://bugs.python.org/issue26075, http://bugs.python.org/issue25830, and http://bugs.python.org/issue26477 - Makes almost everything up to 10x faster. - Is aimed to be a minimalistic change. I only removed issubclass tests from test_typing and main changes to typing are __new__ and __getitem__. The idea is to make most things not classes. Now _ForwardRef(), TypeVar(), Union[], Tuple[], Callable[] are not classes (i.e. new class objects are almost never created by typing). Using isinstance() or issubclass() rises TypeError for almost everything. There are exceptions: Unsubscripted generics are still OK, e.g. issubclass({}, typing.Mapping). This is done to (a) not break existing code by addition of type information; (b) to allow using typing classes as a replacement for collections.abc in class and instance checks. Finally, there is an agreement that a generic without parameters assumes Any, and Any means fallback to dynamic typing. isinstance(lambda x: x, typing.Callable) is also OK. Although Callable is not a generic class, when unsubscribed, it could be also used as a replacement for collections.abc.Callable. The first rule for generics makes isinstance([], typing.List) possible, for consistency I also allowed isinstance((), typing.Tuple). Finally, generics should be classes, to allow subclassing, but now the outcome of __getitem__ on classes is cached. I use an extended version of functools.lru_cache that allows fallback to non-cached version for unhashable arguments. --- python2/test_typing.py | 323 +++++++-------------- python2/typing.py | 586 ++++++++++++++++++------------------- src/test_typing.py | 337 ++++++++-------------- src/typing.py | 641 +++++++++++++++++++---------------------- 4 files changed, 801 insertions(+), 1086 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index 3840fe5e..25472955 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -10,7 +10,7 @@ from typing import TypeVar, AnyStr from typing import T, KT, VT # Not in __all__. from typing import Union, Optional -from typing import Tuple +from typing import Tuple, List, MutableMapping from typing import Callable from typing import Generic, ClassVar from typing import cast @@ -20,6 +20,10 @@ from typing import IO, TextIO, BinaryIO from typing import Pattern, Match import typing +try: + import collections.abc as collections_abc +except ImportError: + import collections as collections_abc # Fallback for PY3.2. class BaseTestCase(TestCase): @@ -61,18 +65,11 @@ def test_any_instance_type_error(self): with self.assertRaises(TypeError): isinstance(42, Any) - def test_any_subclass(self): - self.assertTrue(issubclass(Employee, Any)) - self.assertTrue(issubclass(int, Any)) - self.assertTrue(issubclass(type(None), Any)) - self.assertTrue(issubclass(object, Any)) - - def test_others_any(self): - self.assertFalse(issubclass(Any, Employee)) - self.assertFalse(issubclass(Any, int)) - self.assertFalse(issubclass(Any, type(None))) - # However, Any is a subclass of object (this can't be helped). - self.assertTrue(issubclass(Any, object)) + def test_any_subclass_type_error(self): + with self.assertRaises(TypeError): + issubclass(Employee, Any) + with self.assertRaises(TypeError): + issubclass(Any, Employee) def test_repr(self): self.assertEqual(repr(Any), 'typing.Any') @@ -87,32 +84,21 @@ def test_cannot_subclass(self): with self.assertRaises(TypeError): class A(Any): pass + with self.assertRaises(TypeError): + class A(type(Any)): + pass def test_cannot_instantiate(self): with self.assertRaises(TypeError): Any() + with self.assertRaises(TypeError): + type(Any)() def test_cannot_subscript(self): with self.assertRaises(TypeError): Any[int] def test_any_is_subclass(self): - # Any should be considered a subclass of everything. - self.assertIsSubclass(Any, Any) - self.assertIsSubclass(Any, typing.List) - self.assertIsSubclass(Any, typing.List[int]) - self.assertIsSubclass(Any, typing.List[T]) - self.assertIsSubclass(Any, typing.Mapping) - self.assertIsSubclass(Any, typing.Mapping[str, int]) - self.assertIsSubclass(Any, typing.Mapping[KT, VT]) - self.assertIsSubclass(Any, Generic) - self.assertIsSubclass(Any, Generic[T]) - self.assertIsSubclass(Any, Generic[KT, VT]) - self.assertIsSubclass(Any, AnyStr) - self.assertIsSubclass(Any, Union) - self.assertIsSubclass(Any, Union[int, str]) - self.assertIsSubclass(Any, typing.Match) - self.assertIsSubclass(Any, typing.Match[str]) # These expressions must simply not fail. typing.Match[Any] typing.Pattern[Any] @@ -123,13 +109,8 @@ class TypeVarTests(BaseTestCase): def test_basic_plain(self): T = TypeVar('T') - # Every class is a subclass of T. - self.assertIsSubclass(int, T) - self.assertIsSubclass(str, T) # T equals itself. self.assertEqual(T, T) - # T is a subclass of itself. - self.assertIsSubclass(T, T) # T is an instance of TypeVar self.assertIsInstance(T, TypeVar) @@ -138,16 +119,12 @@ def test_typevar_instance_type_error(self): with self.assertRaises(TypeError): isinstance(42, T) - def test_basic_constrained(self): - A = TypeVar('A', str, bytes) - # Only str and bytes are subclasses of A. - self.assertIsSubclass(str, A) - self.assertIsSubclass(bytes, A) - self.assertNotIsSubclass(int, A) - # A equals itself. - self.assertEqual(A, A) - # A is a subclass of itself. - self.assertIsSubclass(A, A) + def test_typevar_subclass_type_error(self): + T = TypeVar('T') + with self.assertRaises(TypeError): + issubclass(int, T) + with self.assertRaises(TypeError): + issubclass(T, int) def test_constrained_error(self): with self.assertRaises(TypeError): @@ -184,19 +161,6 @@ def test_no_redefinition(self): self.assertNotEqual(TypeVar('T'), TypeVar('T')) self.assertNotEqual(TypeVar('T', int, str), TypeVar('T', int, str)) - def test_subclass_as_unions(self): - # None of these are true -- each type var is its own world. - self.assertFalse(issubclass(TypeVar('T', int, str), - TypeVar('T', int, str))) - self.assertFalse(issubclass(TypeVar('T', int, float), - TypeVar('T', int, float, str))) - self.assertFalse(issubclass(TypeVar('T', int, str), - TypeVar('T', str, int))) - A = TypeVar('A', int, str) - B = TypeVar('B', int, str, float) - self.assertFalse(issubclass(A, B)) - self.assertFalse(issubclass(B, A)) - def test_cannot_subclass_vars(self): with self.assertRaises(TypeError): class V(TypeVar('T')): @@ -211,12 +175,6 @@ def test_cannot_instantiate_vars(self): with self.assertRaises(TypeError): TypeVar('A')() - def test_bound(self): - X = TypeVar('X', bound=Employee) - self.assertIsSubclass(Employee, X) - self.assertIsSubclass(Manager, X) - self.assertNotIsSubclass(int, X) - def test_bound_errors(self): with self.assertRaises(TypeError): TypeVar('X', bound=42) @@ -229,8 +187,16 @@ class UnionTests(BaseTestCase): def test_basics(self): u = Union[int, float] self.assertNotEqual(u, Union) - self.assertTrue(issubclass(int, u)) - self.assertTrue(issubclass(float, u)) + + def test_subclass_error(self): + with self.assertRaises(TypeError): + issubclass(int, Union) + with self.assertRaises(TypeError): + issubclass(Union, int) + with self.assertRaises(TypeError): + issubclass(int, Union[int, str]) + with self.assertRaises(TypeError): + issubclass(Union[int, str], int) def test_union_any(self): u = Union[Any] @@ -259,18 +225,6 @@ def test_unordered(self): u2 = Union[float, int] self.assertEqual(u1, u2) - def test_subclass(self): - u = Union[int, Employee] - self.assertTrue(issubclass(Manager, u)) - - def test_self_subclass(self): - self.assertTrue(issubclass(Union[KT, VT], Union)) - self.assertFalse(issubclass(Union, Union[KT, VT])) - - def test_multiple_inheritance(self): - u = Union[int, Employee] - self.assertTrue(issubclass(ManagingFounder, u)) - def test_single_class_disappears(self): t = Union[Employee] self.assertIs(t, Employee) @@ -283,13 +237,6 @@ def test_base_class_disappears(self): u = Union[Employee, Manager] self.assertIs(u, Employee) - def test_weird_subclasses(self): - u = Union[Employee, int, float] - v = Union[int, float] - self.assertTrue(issubclass(v, u)) - w = Union[int, Manager] - self.assertTrue(issubclass(w, u)) - def test_union_union(self): u = Union[int, float] v = Union[u, Employee] @@ -306,6 +253,9 @@ def test_cannot_subclass(self): with self.assertRaises(TypeError): class C(Union): pass + with self.assertRaises(TypeError): + class C(type(Union)): + pass with self.assertRaises(TypeError): class C(Union[int, str]): pass @@ -316,6 +266,13 @@ def test_cannot_instantiate(self): u = Union[int, float] with self.assertRaises(TypeError): u() + with self.assertRaises(TypeError): + type(u)() + + def test_union_generalization(self): + self.assertFalse(Union[str, typing.Iterable[int]] == str) + self.assertFalse(Union[str, typing.Iterable[int]] == typing.Iterable[int]) + self.assertTrue(Union[str, typing.Iterable] == typing.Iterable) def test_optional(self): o = Optional[int] @@ -326,10 +283,6 @@ def test_empty(self): with self.assertRaises(TypeError): Union[()] - def test_issubclass_union(self): - self.assertIsSubclass(Union[int, str], Union) - self.assertNotIsSubclass(int, Union) - def test_union_instance_type_error(self): with self.assertRaises(TypeError): isinstance(42, Union[int, str]) @@ -354,43 +307,19 @@ def Elem(*args): Union[Elem, str] # Nor should this -class TypeVarUnionTests(BaseTestCase): - - def test_simpler(self): - A = TypeVar('A', int, str, float) - B = TypeVar('B', int, str) - self.assertIsSubclass(A, A) - self.assertIsSubclass(B, B) - self.assertNotIsSubclass(B, A) - self.assertIsSubclass(A, Union[int, str, float]) - self.assertNotIsSubclass(Union[int, str, float], A) - self.assertNotIsSubclass(Union[int, str], B) - self.assertIsSubclass(B, Union[int, str]) - self.assertNotIsSubclass(A, B) - self.assertNotIsSubclass(Union[int, str, float], B) - self.assertNotIsSubclass(A, Union[int, str]) - - def test_var_union_subclass(self): - self.assertTrue(issubclass(T, Union[int, T])) - self.assertTrue(issubclass(KT, Union[KT, VT])) - - def test_var_union(self): - TU = TypeVar('TU', Union[int, float], None) - self.assertIsSubclass(int, TU) - self.assertIsSubclass(float, TU) - - class TupleTests(BaseTestCase): def test_basics(self): - self.assertTrue(issubclass(Tuple[int, str], Tuple)) - self.assertTrue(issubclass(Tuple[int, str], Tuple[int, str])) - self.assertFalse(issubclass(int, Tuple)) - self.assertFalse(issubclass(Tuple[float, str], Tuple[int, str])) - self.assertFalse(issubclass(Tuple[int, str, int], Tuple[int, str])) - self.assertFalse(issubclass(Tuple[int, str], Tuple[int, str, int])) + with self.assertRaises(TypeError): + issubclass(Tuple[int, str], Tuple) + with self.assertRaises(TypeError): + issubclass(Tuple, Tuple[int, str]) + with self.assertRaises(TypeError): + issubclass(tuple, Tuple[int, str]) + + class TP(tuple): pass self.assertTrue(issubclass(tuple, Tuple)) - self.assertFalse(issubclass(Tuple, tuple)) # Can't have it both ways. + self.assertTrue(issubclass(TP, Tuple)) def test_equality(self): self.assertEqual(Tuple[int], Tuple[int]) @@ -406,21 +335,7 @@ class MyTuple(tuple): def test_tuple_instance_type_error(self): with self.assertRaises(TypeError): isinstance((0, 0), Tuple[int, int]) - with self.assertRaises(TypeError): - isinstance((0, 0), Tuple) - - def test_tuple_ellipsis_subclass(self): - - class B(object): - pass - - class C(B): - pass - - self.assertNotIsSubclass(Tuple[B], Tuple[B, ...]) - self.assertIsSubclass(Tuple[C, ...], Tuple[B, ...]) - self.assertNotIsSubclass(Tuple[C, ...], Tuple[B]) - self.assertNotIsSubclass(Tuple[C], Tuple[B, ...]) + isinstance((0, 0), Tuple) def test_repr(self): self.assertEqual(repr(Tuple), 'typing.Tuple') @@ -438,17 +353,9 @@ def test_errors(self): class CallableTests(BaseTestCase): def test_self_subclass(self): - self.assertTrue(issubclass(Callable[[int], int], Callable)) - self.assertFalse(issubclass(Callable, Callable[[int], int])) - self.assertTrue(issubclass(Callable[[int], int], Callable[[int], int])) - self.assertFalse(issubclass(Callable[[Employee], int], - Callable[[Manager], int])) - self.assertFalse(issubclass(Callable[[Manager], int], - Callable[[Employee], int])) - self.assertFalse(issubclass(Callable[[int], Employee], - Callable[[int], Manager])) - self.assertFalse(issubclass(Callable[[int], Manager], - Callable[[int], Employee])) + with self.assertRaises(TypeError): + self.assertTrue(issubclass(type(lambda x: x), Callable[[int], int])) + self.assertTrue(issubclass(type(lambda x: x), Callable)) def test_eq_hash(self): self.assertEqual(Callable[[int], int], Callable[[int], int]) @@ -465,6 +372,11 @@ def test_cannot_subclass(self): class C(Callable): pass + with self.assertRaises(TypeError): + + class C(type(Callable)): + pass + with self.assertRaises(TypeError): class C(Callable[[int], int]): @@ -473,9 +385,13 @@ class C(Callable[[int], int]): def test_cannot_instantiate(self): with self.assertRaises(TypeError): Callable() + with self.assertRaises(TypeError): + type(Callable)() c = Callable[[int], str] with self.assertRaises(TypeError): c() + with self.assertRaises(TypeError): + type(c)() def test_callable_instance_works(self): def f(): @@ -591,6 +507,12 @@ def test_basics(self): with self.assertRaises(TypeError): Y[unicode, unicode] + def test_generic_errors(self): + with self.assertRaises(TypeError): + isinstance([], List[int]) + with self.assertRaises(TypeError): + issubclass(list, List[int]) + def test_init(self): T = TypeVar('T') S = TypeVar('S') @@ -646,6 +568,42 @@ class C(B[int]): c.bar = 'abc' self.assertEqual(c.__dict__, {'bar': 'abc'}) + def test_false_subclasses(self): + class MyMapping(MutableMapping[str, str]): pass + self.assertNotIsInstance({}, MyMapping) + self.assertNotIsSubclass(dict, MyMapping) + + def test_multiple_abc_bases(self): + class MM1(MutableMapping[str, str], collections_abc.MutableMapping): + def __getitem__(self, k): + return None + def __setitem__(self, k, v): + pass + def __delitem__(self, k): + pass + def __iter__(self): + return iter(()) + def __len__(self): + return 0 + class MM2(collections_abc.MutableMapping, MutableMapping[str, str]): + def __getitem__(self, k): + return None + def __setitem__(self, k, v): + pass + def __delitem__(self, k): + pass + def __iter__(self): + return iter(()) + def __len__(self): + return 0 + # these two should just work + MM1().update() + MM2().update() + self.assertIsInstance(MM1(), collections_abc.MutableMapping) + self.assertIsInstance(MM1(), MutableMapping) + self.assertIsInstance(MM2(), collections_abc.MutableMapping) + self.assertIsInstance(MM2(), MutableMapping) + def test_pickle(self): global C # pickle wants to reference the class by name T = TypeVar('T') @@ -828,6 +786,8 @@ class C(type(ClassVar[int])): pass def test_cannot_init(self): + with self.assertRaises(TypeError): + ClassVar() with self.assertRaises(TypeError): type(ClassVar)() with self.assertRaises(TypeError): @@ -840,52 +800,6 @@ def test_no_isinstance(self): issubclass(int, ClassVar) -class VarianceTests(BaseTestCase): - - def test_invariance(self): - # Because of invariance, List[subclass of X] is not a subclass - # of List[X], and ditto for MutableSequence. - self.assertNotIsSubclass(typing.List[Manager], typing.List[Employee]) - self.assertNotIsSubclass(typing.MutableSequence[Manager], - typing.MutableSequence[Employee]) - # It's still reflexive. - self.assertIsSubclass(typing.List[Employee], typing.List[Employee]) - self.assertIsSubclass(typing.MutableSequence[Employee], - typing.MutableSequence[Employee]) - - def test_covariance_tuple(self): - # Check covariace for Tuple (which are really special cases). - self.assertIsSubclass(Tuple[Manager], Tuple[Employee]) - self.assertNotIsSubclass(Tuple[Employee], Tuple[Manager]) - # And pairwise. - self.assertIsSubclass(Tuple[Manager, Manager], - Tuple[Employee, Employee]) - self.assertNotIsSubclass(Tuple[Employee, Employee], - Tuple[Manager, Employee]) - # And using ellipsis. - self.assertIsSubclass(Tuple[Manager, ...], Tuple[Employee, ...]) - self.assertNotIsSubclass(Tuple[Employee, ...], Tuple[Manager, ...]) - - def test_covariance_sequence(self): - # Check covariance for Sequence (which is just a generic class - # for this purpose, but using a type variable with covariant=True). - self.assertIsSubclass(typing.Sequence[Manager], - typing.Sequence[Employee]) - self.assertNotIsSubclass(typing.Sequence[Employee], - typing.Sequence[Manager]) - - def test_covariance_mapping(self): - # Ditto for Mapping (covariant in the value, invariant in the key). - self.assertIsSubclass(typing.Mapping[Employee, Manager], - typing.Mapping[Employee, Employee]) - self.assertNotIsSubclass(typing.Mapping[Manager, Employee], - typing.Mapping[Employee, Employee]) - self.assertNotIsSubclass(typing.Mapping[Employee, Manager], - typing.Mapping[Manager, Manager]) - self.assertNotIsSubclass(typing.Mapping[Manager, Employee], - typing.Mapping[Manager, Manager]) - - class CastTests(BaseTestCase): def test_basics(self): @@ -958,7 +872,6 @@ def test_iterable(self): # path and could fail. So call this a few times. self.assertIsInstance([], typing.Iterable) self.assertIsInstance([], typing.Iterable) - self.assertIsInstance([], typing.Iterable[int]) self.assertNotIsInstance(42, typing.Iterable) # Just in case, also test issubclass() a few times. self.assertIsSubclass(list, typing.Iterable) @@ -967,7 +880,6 @@ def test_iterable(self): def test_iterator(self): it = iter([]) self.assertIsInstance(it, typing.Iterator) - self.assertIsInstance(it, typing.Iterator[int]) self.assertNotIsInstance(42, typing.Iterator) def test_sized(self): @@ -1124,10 +1036,6 @@ def foo(): yield 42 g = foo() self.assertIsSubclass(type(g), typing.Generator) - self.assertIsSubclass(typing.Generator[Manager, Employee, Manager], - typing.Generator[Employee, Manager, Employee]) - self.assertNotIsSubclass(typing.Generator[Manager, Manager, Manager], - typing.Generator[Employee, Employee, Employee]) def test_no_generator_instantiation(self): with self.assertRaises(TypeError): @@ -1274,22 +1182,16 @@ def test_basics(self): pat = re.compile('[a-z]+', re.I) self.assertIsSubclass(pat.__class__, Pattern) self.assertIsSubclass(type(pat), Pattern) - self.assertIsSubclass(type(pat), Pattern[str]) + self.assertIsInstance(pat, Pattern) mat = pat.search('12345abcde.....') self.assertIsSubclass(mat.__class__, Match) - self.assertIsSubclass(mat.__class__, Match[str]) - self.assertIsSubclass(mat.__class__, Match[bytes]) # Sad but true. self.assertIsSubclass(type(mat), Match) - self.assertIsSubclass(type(mat), Match[str]) + self.assertIsInstance(mat, Match) + # these should just work p = Pattern[Union[str, bytes]] - self.assertIsSubclass(Pattern[str], Pattern) - self.assertIsSubclass(Pattern[str], p) - m = Match[Union[bytes, str]] - self.assertIsSubclass(Match[bytes], Match) - self.assertIsSubclass(Match[bytes], m) def test_errors(self): with self.assertRaises(TypeError): @@ -1302,9 +1204,6 @@ def test_errors(self): with self.assertRaises(TypeError): # Too complicated? m[str] - with self.assertRaises(TypeError): - # We don't support isinstance(). - isinstance(42, Pattern) with self.assertRaises(TypeError): # We don't support isinstance(). isinstance(42, Pattern[str]) @@ -1331,7 +1230,7 @@ class A(typing.Match): pass self.assertEqual(str(ex.exception), - "A type alias cannot be subclassed") + "Cannot subclass typing._TypeAlias") class AllTests(BaseTestCase): diff --git a/python2/typing.py b/python2/typing.py index 479da8c3..ae85573d 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -83,6 +83,12 @@ def _qualname(x): # Fall back to just name. return x.__name__ +def _trim_name(nm): + if nm.startswith('_') and nm not in ('_TypeAlias', + '_ForwardRef', '_TypingBase', '_FinalTypingBase'): + nm = nm[1:] + return nm + class TypingMeta(type): """Metaclass for every type defined below. @@ -120,22 +126,68 @@ def _get_type_vars(self, tvars): pass def __repr__(self): - return '%s.%s' % (self.__module__, _qualname(self)) + qname = _trim_name(_qualname(self)) + return '%s.%s' % (self.__module__, qname) + + +class _TypingBase(object): + """Indicator of special typing constructs.""" + __metaclass__ = TypingMeta + __slots__ = () + + def __init__(self, *args, **kwds): + pass + + def __new__(cls, *args, **kwds): + """Constructor. + This only exists to give a better error message in case + someone tries to subclass a special typing object (not a good idea). + """ + if (len(args) == 3 and + isinstance(args[0], str) and + isinstance(args[1], tuple)): + # Close enough. + raise TypeError("Cannot subclass %r" % cls) + return object.__new__(cls) -class Final(object): + # Things that are not classes also need these. + def _eval_type(self, globalns, localns): + return self + + def _get_type_vars(self, tvars): + pass + + def __repr__(self): + cls = type(self) + qname = _trim_name(_qualname(cls)) + return '%s.%s' % (cls.__module__, qname) + + def __call__(self, *args, **kwds): + raise TypeError("Cannot instantiate %r" % type(self)) + + +class _FinalTypingBase(_TypingBase): """Mix-in class to prevent instantiation.""" __slots__ = () - def __new__(self, *args, **kwds): - raise TypeError("Cannot instantiate %r" % self.__class__) + def __new__(cls, *args, **kwds): + self = super(_FinalTypingBase, cls).__new__(cls, *args, **kwds) + if '_root' in kwds and kwds['_root'] is True: + return self + raise TypeError("Cannot instantiate %r" % cls) -class _ForwardRef(TypingMeta): +class _ForwardRef(_TypingBase): """Wrapper to hold a forward reference.""" - def __new__(cls, arg): + __slots__ = ('__forward_arg__', '__forward_code__', + '__forward_evaluated__', '__forward_value__', + '__forward_frame__') + + def __init__(self, arg): + super(_ForwardRef, self).__init__(arg) if not isinstance(arg, basestring): raise TypeError('ForwardRef must be a string -- got %r' % (arg,)) try: @@ -143,7 +195,6 @@ def __new__(cls, arg): except SyntaxError: raise SyntaxError('ForwardRef must be an expression -- got %r' % (arg,)) - self = super(_ForwardRef, cls).__new__(cls, arg, (), {}) self.__forward_arg__ = arg self.__forward_code__ = code self.__forward_evaluated__ = False @@ -154,7 +205,6 @@ def __new__(cls, arg): frame = frame.f_back assert frame is not None self.__forward_frame__ = frame - return self def _eval_type(self, globalns, localns): if not self.__forward_evaluated__: @@ -174,44 +224,23 @@ def __instancecheck__(self, obj): raise TypeError("Forward references cannot be used with isinstance().") def __subclasscheck__(self, cls): - if not self.__forward_evaluated__: - globalns = self.__forward_frame__.f_globals - localns = self.__forward_frame__.f_locals - try: - self._eval_type(globalns, localns) - except NameError: - return False # Too early. - return issubclass(cls, self.__forward_value__) + raise TypeError("Forward references cannot be used with issubclass().") def __repr__(self): return '_ForwardRef(%r)' % (self.__forward_arg__,) -class _TypeAlias(object): +class _TypeAlias(_TypingBase): """Internal helper class for defining generic variants of concrete types. - Note that this is not a type; let's call it a pseudo-type. It can - be used in instance and subclass checks, e.g. isinstance(m, Match) - or issubclass(type(m), Match). However, it cannot be itself the - target of an issubclass() call; e.g. issubclass(Match, C) (for - some arbitrary class C) raises TypeError rather than returning - False. + Note that this is not a type; let's call it a pseudo-type. It cannot + be used in instance and subclass checks in parameterized form, i.e. + ``isinstance(42, Match[str])`` raises ``TypeError`` instead of returning + ``False``. """ __slots__ = ('name', 'type_var', 'impl_type', 'type_checker') - def __new__(cls, *args, **kwds): - """Constructor. - - This only exists to give a better error message in case - someone tries to subclass a type alias (not a good idea). - """ - if (len(args) == 3 and - isinstance(args[0], basestring) and - isinstance(args[1], tuple)): - # Close enough. - raise TypeError("A type alias cannot be subclassed") - return object.__new__(cls) def __init__(self, name, type_var, impl_type, type_checker): """Initializer. @@ -225,9 +254,9 @@ def __init__(self, name, type_var, impl_type, type_checker): and returns a value that should be a type_var instance. """ assert isinstance(name, basestring), repr(name) - assert isinstance(type_var, type), repr(type_var) assert isinstance(impl_type, type), repr(impl_type) assert not isinstance(impl_type, TypingMeta), repr(impl_type) + assert isinstance(type_var, (type, _TypingBase)) self.name = name self.type_var = type_var self.impl_type = impl_type @@ -237,36 +266,33 @@ def __repr__(self): return "%s[%s]" % (self.name, _type_repr(self.type_var)) def __getitem__(self, parameter): - assert isinstance(parameter, type), repr(parameter) if not isinstance(self.type_var, TypeVar): raise TypeError("%s cannot be further parameterized." % self) - if self.type_var.__constraints__: - if not issubclass(parameter, Union[self.type_var.__constraints__]): + if self.type_var.__constraints__ and isinstance(parameter, type): + if not issubclass(parameter, self.type_var.__constraints__): raise TypeError("%s is not a valid substitution for %s." % (parameter, self.type_var)) + if isinstance(parameter, TypeVar): + raise TypeError("%s cannot be re-parameterized." % self.type_var) return self.__class__(self.name, parameter, self.impl_type, self.type_checker) def __instancecheck__(self, obj): - raise TypeError("Type aliases cannot be used with isinstance().") + if not isinstance(self.type_var, TypeVar): + raise TypeError("Parameterized type aliases cannot be used " + "with isinstance().") + return isinstance(obj, self.impl_type) def __subclasscheck__(self, cls): - if cls is Any: - return True - if isinstance(cls, _TypeAlias): - # Covariance. For now, we compare by name. - return (cls.name == self.name and - issubclass(cls.type_var, self.type_var)) - else: - # Note that this is too lenient, because the - # implementation type doesn't carry information about - # whether it is about bytes or str (for example). - return issubclass(cls, self.impl_type) + if not isinstance(self.type_var, TypeVar): + raise TypeError("Parameterized type aliases cannot be used " + "with issubclass().") + return issubclass(cls, self.impl_type) def _get_type_vars(types, tvars): for t in types: - if isinstance(t, TypingMeta) or isinstance(t, _ClassVar): + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): t._get_type_vars(tvars) @@ -277,7 +303,7 @@ def _type_vars(types): def _eval_type(t, globalns, localns): - if isinstance(t, TypingMeta) or isinstance(t, _ClassVar): + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): return t._eval_type(globalns, localns) else: return t @@ -299,7 +325,7 @@ def _type_check(arg, msg): return type(None) if isinstance(arg, basestring): arg = _ForwardRef(arg) - if not isinstance(arg, (type, _TypeAlias)) and not callable(arg): + if not isinstance(arg, (type, _TypingBase)) and not callable(arg): raise TypeError(msg + " Got %.100r." % (arg,)) return arg @@ -330,7 +356,7 @@ def __new__(cls, name, bases, namespace): return self -class _ClassVar(object): +class _ClassVar(_FinalTypingBase): """Special type construct to mark class variables. An annotation wrapped in ClassVar indicates that a given @@ -348,13 +374,10 @@ class Starship: """ __metaclass__ = ClassVarMeta + __slots__ = ('__type__',) def __init__(self, tp=None, _root=False): - cls = type(self) - if _root: - self.__type__ = tp - else: - raise TypeError('Cannot initialize {}'.format(cls.__name__[1:])) + self.__type__ = tp def __getitem__(self, item): cls = type(self) @@ -374,11 +397,10 @@ def _get_type_vars(self, tvars): _get_type_vars(self.__type__, tvars) def __repr__(self): - cls = type(self) - if not self.__type__: - return '{}.{}'.format(cls.__module__, cls.__name__[1:]) - return '{}.{}[{}]'.format(cls.__module__, cls.__name__[1:], - _type_repr(self.__type__)) + r = super(_ClassVar, self).__repr__() + if self.__type__ is not None: + r += '[{}]'.format(_type_repr(self.__type__)) + return r def __hash__(self): return hash((type(self).__name__, self.__type__)) @@ -401,26 +423,26 @@ def __new__(cls, name, bases, namespace): self = super(AnyMeta, cls).__new__(cls, name, bases, namespace) return self - def __instancecheck__(self, obj): - raise TypeError("Any cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - if not isinstance(cls, type): - return super(AnyMeta, cls).__subclasscheck__(cls) # To TypeError. - return True - -class Any(Final): +class _Any(_FinalTypingBase): """Special type indicating an unconstrained type. - Any object is an instance of Any. - Any class is a subclass of Any. - As a special case, Any and object are subclasses of each other. """ - __metaclass__ = AnyMeta __slots__ = () + def __instancecheck__(self, obj): + raise TypeError("Any cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Any cannot be used with issubclass().") + + +Any = _Any(_root=True) + class TypeVarMeta(TypingMeta): def __new__(cls, name, bases, namespace): @@ -428,7 +450,7 @@ def __new__(cls, name, bases, namespace): return super(TypeVarMeta, cls).__new__(cls, name, bases, namespace) -class TypeVar(TypingMeta): +class TypeVar(_TypingBase): """Type variable. Usage:: @@ -474,12 +496,15 @@ def longest(x: A, y: A) -> A: """ __metaclass__ = TypeVarMeta + __slots__ = ('__name__', '__bound__', '__constraints__', + '__covariant__', '__contravariant__') - def __new__(cls, name, *constraints, **kwargs): + def __init__(self, name, *constraints, **kwargs): + super(TypeVar, self).__init__(name, *constraints, **kwargs) bound = kwargs.get('bound', None) covariant = kwargs.get('covariant', False) contravariant = kwargs.get('contravariant', False) - self = super(TypeVar, cls).__new__(cls, name, (Final,), {}) + self.__name__ = name if covariant and contravariant: raise ValueError("Bivariant types are not supported.") self.__covariant__ = bool(covariant) @@ -494,7 +519,6 @@ def __new__(cls, name, *constraints, **kwargs): self.__bound__ = _type_check(bound, "Bound must be a type.") else: self.__bound__ = None - return self def _get_type_vars(self, tvars): if self not in tvars: @@ -513,16 +537,7 @@ def __instancecheck__(self, instance): raise TypeError("Type variables cannot be used with isinstance().") def __subclasscheck__(self, cls): - # TODO: Make this raise TypeError too? - if cls is self: - return True - if cls is Any: - return True - if self.__bound__ is not None: - return issubclass(cls, self.__bound__) - if self.__constraints__: - return any(issubclass(cls, c) for c in self.__constraints__) - return True + raise TypeError("Type variables cannot be used with issubclass().") # Some unconstrained type variables. These are used by the container types. @@ -543,17 +558,79 @@ def __subclasscheck__(self, cls): class UnionMeta(TypingMeta): """Metaclass for Union.""" - def __new__(cls, name, bases, namespace, parameters=None): + def __new__(cls, name, bases, namespace): cls.assert_no_subclassing(bases) + return super(UnionMeta, cls).__new__(cls, name, bases, namespace) + + +class _Union(_FinalTypingBase): + """Union type; Union[X, Y] means either X or Y. + + To define a union, use e.g. Union[int, str]. Details: + + - The arguments must be types and there must be at least one. + + - None as an argument is a special case and is replaced by + type(None). + + - Unions of unions are flattened, e.g.:: + + Union[Union[int, str], float] == Union[int, str, float] + + - Unions of a single argument vanish, e.g.:: + + Union[int] == int # The constructor actually returns int + + - Redundant arguments are skipped, e.g.:: + + Union[int, str, int] == Union[int, str] + + - When comparing unions, the argument order is ignored, e.g.:: + + Union[int, str] == Union[str, int] + + - When two arguments have a subclass relationship, the least + derived argument is kept, e.g.:: + + class Employee: pass + class Manager(Employee): pass + Union[int, Employee, Manager] == Union[int, Employee] + Union[Manager, int, Employee] == Union[int, Employee] + Union[Employee, Manager] == Employee + + - Corollary: if Any is present it is the sole survivor, e.g.:: + + Union[int, Any] == Any + + - Similar for object:: + + Union[int, object] == object + + - To cut a tie: Union[object, Any] == Union[Any, object] == Any. + + - You cannot subclass or instantiate a union. + + - You cannot write Union[X][Y] (what would it mean?). + + - You can use Optional[X] as a shorthand for Union[X, None]. + """ + + __metaclass__ = UnionMeta + __slots__ = ('__union_params__', '__union_set_params__') + + def __new__(cls, parameters=None, *args, **kwds): + self = super(_Union, cls).__new__(cls, parameters, *args, **kwds) if parameters is None: - return super(UnionMeta, cls).__new__(cls, name, bases, namespace) + self.__union_params__ = None + self.__union_set_params__ = None + return self if not isinstance(parameters, tuple): raise TypeError("Expected parameters=") # Flatten out Union[Union[...], ...] and type-check non-Union args. params = [] msg = "Union[arg, ...]: each arg must be a type." for p in parameters: - if isinstance(p, UnionMeta): + if isinstance(p, _Union): params.extend(p.__union_params__) else: params.append(_type_check(p, msg)) @@ -577,22 +654,16 @@ def __new__(cls, name, bases, namespace, parameters=None): for t1 in params: if t1 is Any: return Any - if isinstance(t1, TypeVar): - continue - if isinstance(t1, _TypeAlias): - # _TypeAlias is not a real class. - continue if not isinstance(t1, type): - assert callable(t1) # A callable might sneak through. continue if any(isinstance(t2, type) and issubclass(t1, t2) - for t2 in all_params - {t1} if not isinstance(t2, TypeVar)): + for t2 in all_params - {t1} + if not (isinstance(t2, GenericMeta) and + t2.__origin__ is not None)): all_params.remove(t1) # It's not a union if there's only one type left. if len(all_params) == 1: return all_params.pop() - # Create a new class with these params. - self = super(UnionMeta, cls).__new__(cls, name, bases, {}) self.__union_params__ = tuple(t for t in params if t in all_params) self.__union_set_params__ = frozenset(self.__union_params__) return self @@ -603,15 +674,14 @@ def _eval_type(self, globalns, localns): if p == self.__union_params__: return self else: - return self.__class__(self.__name__, self.__bases__, {}, - p) + return self.__class__(p, _root=True) def _get_type_vars(self, tvars): if self.__union_params__: _get_type_vars(self.__union_params__, tvars) def __repr__(self): - r = super(UnionMeta, self).__repr__() + r = super(_Union, self).__repr__() if self.__union_params__: r += '[%s]' % (', '.join(_type_repr(t) for t in self.__union_params__)) @@ -625,11 +695,10 @@ def __getitem__(self, parameters): raise TypeError("Cannot take a Union of no types.") if not isinstance(parameters, tuple): parameters = (parameters,) - return self.__class__(self.__name__, self.__bases__, - dict(self.__dict__), parameters) + return self.__class__(parameters, _root=True) def __eq__(self, other): - if not isinstance(other, UnionMeta): + if not isinstance(other, _Union): return NotImplemented return self.__union_set_params__ == other.__union_set_params__ @@ -640,81 +709,10 @@ def __instancecheck__(self, obj): raise TypeError("Unions cannot be used with isinstance().") def __subclasscheck__(self, cls): - if cls is Any: - return True - if self.__union_params__ is None: - return isinstance(cls, UnionMeta) - elif isinstance(cls, UnionMeta): - if cls.__union_params__ is None: - return False - return all(issubclass(c, self) for c in (cls.__union_params__)) - elif isinstance(cls, TypeVar): - if cls in self.__union_params__: - return True - if cls.__constraints__: - return issubclass(Union[cls.__constraints__], self) - return False - else: - return any(issubclass(cls, t) for t in self.__union_params__) + raise TypeError("Unions cannot be used with issubclass().") -class Union(Final): - """Union type; Union[X, Y] means either X or Y. - - To define a union, use e.g. Union[int, str]. Details: - - - The arguments must be types and there must be at least one. - - - None as an argument is a special case and is replaced by - type(None). - - - Unions of unions are flattened, e.g.:: - - Union[Union[int, str], float] == Union[int, str, float] - - - Unions of a single argument vanish, e.g.:: - - Union[int] == int # The constructor actually returns int - - - Redundant arguments are skipped, e.g.:: - - Union[int, str, int] == Union[int, str] - - - When comparing unions, the argument order is ignored, e.g.:: - - Union[int, str] == Union[str, int] - - - When two arguments have a subclass relationship, the least - derived argument is kept, e.g.:: - - class Employee: pass - class Manager(Employee): pass - Union[int, Employee, Manager] == Union[int, Employee] - Union[Manager, int, Employee] == Union[int, Employee] - Union[Employee, Manager] == Employee - - - Corollary: if Any is present it is the sole survivor, e.g.:: - - Union[int, Any] == Any - - - Similar for object:: - - Union[int, object] == object - - - To cut a tie: Union[object, Any] == Union[Any, object] == Any. - - - You cannot subclass or instantiate a union. - - - You cannot write Union[X][Y] (what would it mean?). - - - You can use Optional[X] as a shorthand for Union[X, None]. - """ - - __metaclass__ = UnionMeta - - # Unsubscripted Union type has params set to None. - __union_params__ = None - __union_set_params__ = None +Union = _Union(_root=True) class OptionalMeta(TypingMeta): @@ -724,12 +722,8 @@ def __new__(cls, name, bases, namespace): cls.assert_no_subclassing(bases) return super(OptionalMeta, cls).__new__(cls, name, bases, namespace) - def __getitem__(self, arg): - arg = _type_check(arg, "Optional[t] requires a single type.") - return Union[arg, type(None)] - -class Optional(Final): +class _Optional(_FinalTypingBase): """Optional type. Optional[X] is equivalent to Union[X, type(None)]. @@ -738,17 +732,39 @@ class Optional(Final): __metaclass__ = OptionalMeta __slots__ = () + def __getitem__(self, arg): + arg = _type_check(arg, "Optional[t] requires a single type.") + return Union[arg, type(None)] + + +Optional = _Optional(_root=True) + class TupleMeta(TypingMeta): """Metaclass for Tuple.""" - def __new__(cls, name, bases, namespace, parameters=None, - use_ellipsis=False): + def __new__(cls, name, bases, namespace): cls.assert_no_subclassing(bases) - self = super(TupleMeta, cls).__new__(cls, name, bases, namespace) + return super(TupleMeta, cls).__new__(cls, name, bases, namespace) + + +class _Tuple(_FinalTypingBase): + """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. + + Example: Tuple[T1, T2] is a tuple of two elements corresponding + to type variables T1 and T2. Tuple[int, float, str] is a tuple + of an int, a float and a string. + + To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. + """ + + __metaclass__ = TupleMeta + __slots__ = ('__tuple_params__', '__tuple_use_ellipsis__') + + def __init__(self, parameters=None, + use_ellipsis=False, _root=False): self.__tuple_params__ = parameters self.__tuple_use_ellipsis__ = use_ellipsis - return self def _get_type_vars(self, tvars): if self.__tuple_params__: @@ -762,11 +778,10 @@ def _eval_type(self, globalns, localns): if p == self.__tuple_params__: return self else: - return self.__class__(self.__name__, self.__bases__, {}, - p) + return self.__class__(p, _root=True) def __repr__(self): - r = super(TupleMeta, self).__repr__() + r = super(_Tuple, self).__repr__() if self.__tuple_params__ is not None: params = [_type_repr(p) for p in self.__tuple_params__] if self.__tuple_use_ellipsis__: @@ -790,12 +805,10 @@ def __getitem__(self, parameters): use_ellipsis = False msg = "Tuple[t0, t1, ...]: each t must be a type." parameters = tuple(_type_check(p, msg) for p in parameters) - return self.__class__(self.__name__, self.__bases__, - dict(self.__dict__), parameters, - use_ellipsis=use_ellipsis) + return self.__class__(parameters, use_ellipsis=use_ellipsis, _root=True) def __eq__(self, other): - if not isinstance(other, TupleMeta): + if not isinstance(other, _Tuple): return NotImplemented return (self.__tuple_params__ == other.__tuple_params__ and self.__tuple_use_ellipsis__ == other.__tuple_use_ellipsis__) @@ -804,51 +817,44 @@ def __hash__(self): return hash(self.__tuple_params__) def __instancecheck__(self, obj): - raise TypeError("Tuples cannot be used with isinstance().") + if self.__tuple_params__ == None: + return isinstance(obj, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with isinstance().") def __subclasscheck__(self, cls): - if cls is Any: - return True - if not isinstance(cls, type): - # To TypeError. - return super(TupleMeta, self).__subclasscheck__(cls) - if issubclass(cls, tuple): - return True # Special case. - if not isinstance(cls, TupleMeta): - return super(TupleMeta, self).__subclasscheck__(cls) # False. - if self.__tuple_params__ is None: - return True - if cls.__tuple_params__ is None: - return False # ??? - if cls.__tuple_use_ellipsis__ != self.__tuple_use_ellipsis__: - return False - # Covariance. - return (len(self.__tuple_params__) == len(cls.__tuple_params__) and - all(issubclass(x, p) - for x, p in zip(cls.__tuple_params__, - self.__tuple_params__))) - - -class Tuple(Final): - """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. + if self.__tuple_params__ == None: + return issubclass(cls, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with issubclass().") - Example: Tuple[T1, T2] is a tuple of two elements corresponding - to type variables T1 and T2. Tuple[int, float, str] is a tuple - of an int, a float and a string. - To specify a variable-length tuple of homogeneous type, use Sequence[T]. - """ - - __metaclass__ = TupleMeta - __slots__ = () +Tuple = _Tuple(_root=True) class CallableMeta(TypingMeta): """Metaclass for Callable.""" - def __new__(cls, name, bases, namespace, - args=None, result=None): + def __new__(cls, name, bases, namespace): cls.assert_no_subclassing(bases) + return super(CallableMeta, cls).__new__(cls, name, bases, namespace) + + +class _Callable(_FinalTypingBase): + """Callable type; Callable[[int], str] is a function of (int) -> str. + + The subscription syntax must always be used with exactly two + values: the argument list and the return type. The argument list + must be a list of types; the return type must be a single type. + + There is no syntax to indicate optional or keyword arguments, + such function types are rarely used as callback types. + """ + + __metaclass__ = CallableMeta + __slots__ = ('__args__', '__result__') + + def __init__(self, args=None, result=None, _root=False): if args is None and result is None: pass # Must be 'class Callable'. else: @@ -861,10 +867,8 @@ def __new__(cls, name, bases, namespace, args = tuple(_type_check(arg, msg) for arg in args) msg = "Callable[args, result]: result must be a type." result = _type_check(result, msg) - self = super(CallableMeta, cls).__new__(cls, name, bases, namespace) self.__args__ = args self.__result__ = result - return self def _get_type_vars(self, tvars): if self.__args__ and self.__args__ is not Ellipsis: @@ -881,11 +885,10 @@ def _eval_type(self, globalns, localns): if args == self.__args__ and result == self.__result__: return self else: - return self.__class__(self.__name__, self.__bases__, {}, - args=args, result=result) + return self.__class__(args=args, result=result, _root=True) def __repr__(self): - r = super(CallableMeta, self).__repr__() + r = super(_Callable, self).__repr__() if self.__args__ is not None or self.__result__ is not None: if self.__args__ is Ellipsis: args_r = '...' @@ -902,12 +905,10 @@ def __getitem__(self, parameters): raise TypeError( "Callable must be used as Callable[[arg, ...], result].") args, result = parameters - return self.__class__(self.__name__, self.__bases__, - dict(self.__dict__), - args=args, result=result) + return self.__class__(args=args, result=result, _root=True) def __eq__(self, other): - if not isinstance(other, CallableMeta): + if not isinstance(other, _Callable): return NotImplemented return (self.__args__ == other.__args__ and self.__result__ == other.__result__) @@ -922,32 +923,18 @@ def __instancecheck__(self, obj): if self.__args__ is None and self.__result__ is None: return isinstance(obj, collections_abc.Callable) else: - raise TypeError("Callable[] cannot be used with isinstance().") + raise TypeError("Parameterized Callable cannot be used " + "with isinstance().") def __subclasscheck__(self, cls): - if cls is Any: - return True - if not isinstance(cls, CallableMeta): - return super(CallableMeta, self).__subclasscheck__(cls) if self.__args__ is None and self.__result__ is None: - return True - # We're not doing covariance or contravariance -- this is *invariance*. - return self == cls - - -class Callable(Final): - """Callable type; Callable[[int], str] is a function of (int) -> str. - - The subscription syntax must always be used with exactly two - values: the argument list and the return type. The argument list - must be a list of types; the return type must be a single type. + return issubclass(cls, collections_abc.Callable) + else: + raise TypeError("Parameterized Callable cannot be used " + "with issubclass().") - There is no syntax to indicate optional or keyword arguments, - such function types are rarely used as callback types. - """ - __metaclass__ = CallableMeta - __slots__ = () +Callable = _Callable(_root=True) def _gorg(a): @@ -1127,44 +1114,18 @@ def __instancecheck__(self, instance): return self.__subclasscheck__(instance.__class__) def __subclasscheck__(self, cls): - if cls is Any: - return True - if isinstance(cls, GenericMeta): - # For a covariant class C(Generic[T]), - # C[X] is a subclass of C[Y] iff X is a subclass of Y. - origin = self.__origin__ - if origin is not None and origin is cls.__origin__: - assert len(self.__args__) == len(origin.__parameters__) - assert len(cls.__args__) == len(origin.__parameters__) - for p_self, p_cls, p_origin in zip(self.__args__, - cls.__args__, - origin.__parameters__): - if isinstance(p_origin, TypeVar): - if p_origin.__covariant__: - # Covariant -- p_cls must be a subclass of p_self. - if not issubclass(p_cls, p_self): - break - elif p_origin.__contravariant__: - # Contravariant. I think it's the opposite. :-) - if not issubclass(p_self, p_cls): - break - else: - # Invariant -- p_cls and p_self must equal. - if p_self != p_cls: - break - else: - # If the origin's parameter is not a typevar, - # insist on invariance. - if p_self != p_cls: - break - else: - return True - # If we break out of the loop, the superclass gets a chance. + if self is Generic: + raise TypeError("Class %r cannot be used with class " + "or instance checks" % self) + if (self.__origin__ is not None and + sys._getframe(1).f_globals['__name__'] != 'abc'): + raise TypeError("Parameterized generics cannot be used with class " + "or instance checks") if super(GenericMeta, self).__subclasscheck__(cls): return True - if self.__extra__ is None or isinstance(cls, GenericMeta): - return False - return issubclass(cls, self.__extra__) + if self.__extra__ is not None: + return issubclass(cls, self.__extra__) + return False # Prevent checks for Generic to crash when defining Generic. @@ -1465,31 +1426,38 @@ class Container(Generic[T_co]): class AbstractSet(Sized, Iterable[T_co], Container[T_co]): + __slots__ = () __extra__ = collections_abc.Set class MutableSet(AbstractSet[T]): + __slots__ = () __extra__ = collections_abc.MutableSet # NOTE: It is only covariant in the value type. class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co]): + __slots__ = () __extra__ = collections_abc.Mapping class MutableMapping(Mapping[KT, VT]): + __slots__ = () __extra__ = collections_abc.MutableMapping if hasattr(collections_abc, 'Reversible'): class Sequence(Sized, Reversible[T_co], Container[T_co]): + __slots__ = () __extra__ = collections_abc.Sequence else: class Sequence(Sized, Iterable[T_co], Container[T_co]): + __slots__ = () __extra__ = collections_abc.Sequence class MutableSequence(Sequence[T]): + __slots__ = () __extra__ = collections_abc.MutableSequence @@ -1502,6 +1470,7 @@ class ByteString(Sequence[int]): class List(list, MutableSequence[T]): + __slots__ = () __extra__ = list def __new__(cls, *args, **kwds): @@ -1512,6 +1481,7 @@ def __new__(cls, *args, **kwds): class Set(set, MutableSet[T]): + __slots__ = () __extra__ = set def __new__(cls, *args, **kwds): @@ -1521,22 +1491,7 @@ def __new__(cls, *args, **kwds): return set.__new__(cls, *args, **kwds) -class _FrozenSetMeta(GenericMeta): - """This metaclass ensures set is not a subclass of FrozenSet. - - Without this metaclass, set would be considered a subclass of - FrozenSet, because FrozenSet.__extra__ is collections.abc.Set, and - set is a subclass of that. - """ - - def __subclasscheck__(self, cls): - if issubclass(cls, Set): - return False - return super(_FrozenSetMeta, self).__subclasscheck__(cls) - - class FrozenSet(frozenset, AbstractSet[T_co]): - __metaclass__ = _FrozenSetMeta __slots__ = () __extra__ = frozenset @@ -1548,24 +1503,29 @@ def __new__(cls, *args, **kwds): class MappingView(Sized, Iterable[T_co]): + __slots__ = () __extra__ = collections_abc.MappingView class KeysView(MappingView[KT], AbstractSet[KT]): + __slots__ = () __extra__ = collections_abc.KeysView class ItemsView(MappingView[Tuple[KT, VT_co]], AbstractSet[Tuple[KT, VT_co]], Generic[KT, VT_co]): + __slots__ = () __extra__ = collections_abc.ItemsView class ValuesView(MappingView[VT_co]): + __slots__ = () __extra__ = collections_abc.ValuesView class Dict(dict, MutableMapping[KT, VT]): + __slots__ = () __extra__ = dict def __new__(cls, *args, **kwds): @@ -1576,6 +1536,7 @@ def __new__(cls, *args, **kwds): class DefaultDict(collections.defaultdict, MutableMapping[KT, VT]): + __slots__ = () __extra__ = collections.defaultdict def __new__(cls, *args, **kwds): @@ -1633,6 +1594,7 @@ def new_user(user_class: Type[U]) -> U: At this point the type checker knows that joe has type BasicUser. """ + __slots__ = () __extra__ = type diff --git a/src/test_typing.py b/src/test_typing.py index 3b99060f..6543005f 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -9,7 +9,7 @@ from typing import TypeVar, AnyStr from typing import T, KT, VT # Not in __all__. from typing import Union, Optional -from typing import Tuple, List +from typing import Tuple, List, MutableMapping from typing import Callable from typing import Generic, ClassVar from typing import cast @@ -21,6 +21,10 @@ from typing import IO, TextIO, BinaryIO from typing import Pattern, Match import typing +try: + import collections.abc as collections_abc +except ImportError: + import collections as collections_abc # Fallback for PY3.2. class BaseTestCase(TestCase): @@ -62,18 +66,11 @@ def test_any_instance_type_error(self): with self.assertRaises(TypeError): isinstance(42, Any) - def test_any_subclass(self): - self.assertTrue(issubclass(Employee, Any)) - self.assertTrue(issubclass(int, Any)) - self.assertTrue(issubclass(type(None), Any)) - self.assertTrue(issubclass(object, Any)) - - def test_others_any(self): - self.assertFalse(issubclass(Any, Employee)) - self.assertFalse(issubclass(Any, int)) - self.assertFalse(issubclass(Any, type(None))) - # However, Any is a subclass of object (this can't be helped). - self.assertTrue(issubclass(Any, object)) + def test_any_subclass_type_error(self): + with self.assertRaises(TypeError): + issubclass(Employee, Any) + with self.assertRaises(TypeError): + issubclass(Any, Employee) def test_repr(self): self.assertEqual(repr(Any), 'typing.Any') @@ -88,32 +85,21 @@ def test_cannot_subclass(self): with self.assertRaises(TypeError): class A(Any): pass + with self.assertRaises(TypeError): + class A(type(Any)): + pass def test_cannot_instantiate(self): with self.assertRaises(TypeError): Any() + with self.assertRaises(TypeError): + type(Any)() def test_cannot_subscript(self): with self.assertRaises(TypeError): Any[int] - def test_any_is_subclass(self): - # Any should be considered a subclass of everything. - self.assertIsSubclass(Any, Any) - self.assertIsSubclass(Any, typing.List) - self.assertIsSubclass(Any, typing.List[int]) - self.assertIsSubclass(Any, typing.List[T]) - self.assertIsSubclass(Any, typing.Mapping) - self.assertIsSubclass(Any, typing.Mapping[str, int]) - self.assertIsSubclass(Any, typing.Mapping[KT, VT]) - self.assertIsSubclass(Any, Generic) - self.assertIsSubclass(Any, Generic[T]) - self.assertIsSubclass(Any, Generic[KT, VT]) - self.assertIsSubclass(Any, AnyStr) - self.assertIsSubclass(Any, Union) - self.assertIsSubclass(Any, Union[int, str]) - self.assertIsSubclass(Any, typing.Match) - self.assertIsSubclass(Any, typing.Match[str]) + def test_any_works_with_alias(self): # These expressions must simply not fail. typing.Match[Any] typing.Pattern[Any] @@ -124,13 +110,8 @@ class TypeVarTests(BaseTestCase): def test_basic_plain(self): T = TypeVar('T') - # Every class is a subclass of T. - self.assertIsSubclass(int, T) - self.assertIsSubclass(str, T) # T equals itself. self.assertEqual(T, T) - # T is a subclass of itself. - self.assertIsSubclass(T, T) # T is an instance of TypeVar self.assertIsInstance(T, TypeVar) @@ -139,16 +120,12 @@ def test_typevar_instance_type_error(self): with self.assertRaises(TypeError): isinstance(42, T) - def test_basic_constrained(self): - A = TypeVar('A', str, bytes) - # Only str and bytes are subclasses of A. - self.assertIsSubclass(str, A) - self.assertIsSubclass(bytes, A) - self.assertNotIsSubclass(int, A) - # A equals itself. - self.assertEqual(A, A) - # A is a subclass of itself. - self.assertIsSubclass(A, A) + def test_typevar_subclass_type_error(self): + T = TypeVar('T') + with self.assertRaises(TypeError): + issubclass(int, T) + with self.assertRaises(TypeError): + issubclass(T, int) def test_constrained_error(self): with self.assertRaises(TypeError): @@ -185,19 +162,6 @@ def test_no_redefinition(self): self.assertNotEqual(TypeVar('T'), TypeVar('T')) self.assertNotEqual(TypeVar('T', int, str), TypeVar('T', int, str)) - def test_subclass_as_unions(self): - # None of these are true -- each type var is its own world. - self.assertFalse(issubclass(TypeVar('T', int, str), - TypeVar('T', int, str))) - self.assertFalse(issubclass(TypeVar('T', int, float), - TypeVar('T', int, float, str))) - self.assertFalse(issubclass(TypeVar('T', int, str), - TypeVar('T', str, int))) - A = TypeVar('A', int, str) - B = TypeVar('B', int, str, float) - self.assertFalse(issubclass(A, B)) - self.assertFalse(issubclass(B, A)) - def test_cannot_subclass_vars(self): with self.assertRaises(TypeError): class V(TypeVar('T')): @@ -212,12 +176,6 @@ def test_cannot_instantiate_vars(self): with self.assertRaises(TypeError): TypeVar('A')() - def test_bound(self): - X = TypeVar('X', bound=Employee) - self.assertIsSubclass(Employee, X) - self.assertIsSubclass(Manager, X) - self.assertNotIsSubclass(int, X) - def test_bound_errors(self): with self.assertRaises(TypeError): TypeVar('X', bound=42) @@ -230,8 +188,16 @@ class UnionTests(BaseTestCase): def test_basics(self): u = Union[int, float] self.assertNotEqual(u, Union) - self.assertTrue(issubclass(int, u)) - self.assertTrue(issubclass(float, u)) + + def test_subclass_error(self): + with self.assertRaises(TypeError): + issubclass(int, Union) + with self.assertRaises(TypeError): + issubclass(Union, int) + with self.assertRaises(TypeError): + issubclass(int, Union[int, str]) + with self.assertRaises(TypeError): + issubclass(Union[int, str], int) def test_union_any(self): u = Union[Any] @@ -260,18 +226,6 @@ def test_unordered(self): u2 = Union[float, int] self.assertEqual(u1, u2) - def test_subclass(self): - u = Union[int, Employee] - self.assertTrue(issubclass(Manager, u)) - - def test_self_subclass(self): - self.assertTrue(issubclass(Union[KT, VT], Union)) - self.assertFalse(issubclass(Union, Union[KT, VT])) - - def test_multiple_inheritance(self): - u = Union[int, Employee] - self.assertTrue(issubclass(ManagingFounder, u)) - def test_single_class_disappears(self): t = Union[Employee] self.assertIs(t, Employee) @@ -284,13 +238,6 @@ def test_base_class_disappears(self): u = Union[Employee, Manager] self.assertIs(u, Employee) - def test_weird_subclasses(self): - u = Union[Employee, int, float] - v = Union[int, float] - self.assertTrue(issubclass(v, u)) - w = Union[int, Manager] - self.assertTrue(issubclass(w, u)) - def test_union_union(self): u = Union[int, float] v = Union[u, Employee] @@ -307,6 +254,9 @@ def test_cannot_subclass(self): with self.assertRaises(TypeError): class C(Union): pass + with self.assertRaises(TypeError): + class C(type(Union)): + pass with self.assertRaises(TypeError): class C(Union[int, str]): pass @@ -314,9 +264,18 @@ class C(Union[int, str]): def test_cannot_instantiate(self): with self.assertRaises(TypeError): Union() + with self.assertRaises(TypeError): + type(Union)() u = Union[int, float] with self.assertRaises(TypeError): u() + with self.assertRaises(TypeError): + type(u)() + + def test_union_generalization(self): + self.assertFalse(Union[str, typing.Iterable[int]] == str) + self.assertFalse(Union[str, typing.Iterable[int]] == typing.Iterable[int]) + self.assertTrue(Union[str, typing.Iterable] == typing.Iterable) def test_optional(self): o = Optional[int] @@ -327,10 +286,6 @@ def test_empty(self): with self.assertRaises(TypeError): Union[()] - def test_issubclass_union(self): - self.assertIsSubclass(Union[int, str], Union) - self.assertNotIsSubclass(int, Union) - def test_union_instance_type_error(self): with self.assertRaises(TypeError): isinstance(42, Union[int, str]) @@ -355,43 +310,19 @@ def Elem(*args): Union[Elem, str] # Nor should this -class TypeVarUnionTests(BaseTestCase): - - def test_simpler(self): - A = TypeVar('A', int, str, float) - B = TypeVar('B', int, str) - self.assertIsSubclass(A, A) - self.assertIsSubclass(B, B) - self.assertNotIsSubclass(B, A) - self.assertIsSubclass(A, Union[int, str, float]) - self.assertNotIsSubclass(Union[int, str, float], A) - self.assertNotIsSubclass(Union[int, str], B) - self.assertIsSubclass(B, Union[int, str]) - self.assertNotIsSubclass(A, B) - self.assertNotIsSubclass(Union[int, str, float], B) - self.assertNotIsSubclass(A, Union[int, str]) - - def test_var_union_subclass(self): - self.assertTrue(issubclass(T, Union[int, T])) - self.assertTrue(issubclass(KT, Union[KT, VT])) - - def test_var_union(self): - TU = TypeVar('TU', Union[int, float], None) - self.assertIsSubclass(int, TU) - self.assertIsSubclass(float, TU) - - class TupleTests(BaseTestCase): def test_basics(self): - self.assertTrue(issubclass(Tuple[int, str], Tuple)) - self.assertTrue(issubclass(Tuple[int, str], Tuple[int, str])) - self.assertFalse(issubclass(int, Tuple)) - self.assertFalse(issubclass(Tuple[float, str], Tuple[int, str])) - self.assertFalse(issubclass(Tuple[int, str, int], Tuple[int, str])) - self.assertFalse(issubclass(Tuple[int, str], Tuple[int, str, int])) + with self.assertRaises(TypeError): + issubclass(Tuple[int, str], Tuple) + with self.assertRaises(TypeError): + issubclass(Tuple, Tuple[int, str]) + with self.assertRaises(TypeError): + issubclass(tuple, Tuple[int, str]) + + class TP(tuple): ... self.assertTrue(issubclass(tuple, Tuple)) - self.assertFalse(issubclass(Tuple, tuple)) # Can't have it both ways. + self.assertTrue(issubclass(TP, Tuple)) def test_equality(self): self.assertEqual(Tuple[int], Tuple[int]) @@ -407,21 +338,7 @@ class MyTuple(tuple): def test_tuple_instance_type_error(self): with self.assertRaises(TypeError): isinstance((0, 0), Tuple[int, int]) - with self.assertRaises(TypeError): - isinstance((0, 0), Tuple) - - def test_tuple_ellipsis_subclass(self): - - class B: - pass - - class C(B): - pass - - self.assertNotIsSubclass(Tuple[B], Tuple[B, ...]) - self.assertIsSubclass(Tuple[C, ...], Tuple[B, ...]) - self.assertNotIsSubclass(Tuple[C, ...], Tuple[B]) - self.assertNotIsSubclass(Tuple[C], Tuple[B, ...]) + self.assertIsInstance((0, 0), Tuple) def test_repr(self): self.assertEqual(repr(Tuple), 'typing.Tuple') @@ -439,17 +356,9 @@ def test_errors(self): class CallableTests(BaseTestCase): def test_self_subclass(self): - self.assertTrue(issubclass(Callable[[int], int], Callable)) - self.assertFalse(issubclass(Callable, Callable[[int], int])) - self.assertTrue(issubclass(Callable[[int], int], Callable[[int], int])) - self.assertFalse(issubclass(Callable[[Employee], int], - Callable[[Manager], int])) - self.assertFalse(issubclass(Callable[[Manager], int], - Callable[[Employee], int])) - self.assertFalse(issubclass(Callable[[int], Employee], - Callable[[int], Manager])) - self.assertFalse(issubclass(Callable[[int], Manager], - Callable[[int], Employee])) + with self.assertRaises(TypeError): + self.assertTrue(issubclass(type(lambda x: x), Callable[[int], int])) + self.assertTrue(issubclass(type(lambda x: x), Callable)) def test_eq_hash(self): self.assertEqual(Callable[[int], int], Callable[[int], int]) @@ -466,6 +375,11 @@ def test_cannot_subclass(self): class C(Callable): pass + with self.assertRaises(TypeError): + + class C(type(Callable)): + pass + with self.assertRaises(TypeError): class C(Callable[[int], int]): @@ -474,9 +388,13 @@ class C(Callable[[int], int]): def test_cannot_instantiate(self): with self.assertRaises(TypeError): Callable() + with self.assertRaises(TypeError): + type(Callable)() c = Callable[[int], str] with self.assertRaises(TypeError): c() + with self.assertRaises(TypeError): + type(c)() def test_callable_instance_works(self): def f(): @@ -616,6 +534,12 @@ def test_basics(self): with self.assertRaises(TypeError): Y[str, str] + def test_generic_errors(self): + with self.assertRaises(TypeError): + isinstance([], List[int]) + with self.assertRaises(TypeError): + issubclass(list, List[int]) + def test_init(self): T = TypeVar('T') S = TypeVar('S') @@ -671,6 +595,42 @@ class C(B[int]): c.bar = 'abc' self.assertEqual(c.__dict__, {'bar': 'abc'}) + def test_false_subclasses(self): + class MyMapping(MutableMapping[str, str]): pass + self.assertNotIsInstance({}, MyMapping) + self.assertNotIsSubclass(dict, MyMapping) + + def test_multiple_abc_bases(self): + class MM1(MutableMapping[str, str], collections_abc.MutableMapping): + def __getitem__(self, k): + return None + def __setitem__(self, k, v): + pass + def __delitem__(self, k): + pass + def __iter__(self): + return iter(()) + def __len__(self): + return 0 + class MM2(collections_abc.MutableMapping, MutableMapping[str, str]): + def __getitem__(self, k): + return None + def __setitem__(self, k, v): + pass + def __delitem__(self, k): + pass + def __iter__(self): + return iter(()) + def __len__(self): + return 0 + # these two should just work + MM1().update() + MM2().update() + self.assertIsInstance(MM1(), collections_abc.MutableMapping) + self.assertIsInstance(MM1(), MutableMapping) + self.assertIsInstance(MM2(), collections_abc.MutableMapping) + self.assertIsInstance(MM2(), MutableMapping) + def test_pickle(self): global C # pickle wants to reference the class by name T = TypeVar('T') @@ -853,6 +813,8 @@ class C(type(ClassVar[int])): pass def test_cannot_init(self): + with self.assertRaises(TypeError): + ClassVar() with self.assertRaises(TypeError): type(ClassVar)() with self.assertRaises(TypeError): @@ -865,52 +827,6 @@ def test_no_isinstance(self): issubclass(int, ClassVar) -class VarianceTests(BaseTestCase): - - def test_invariance(self): - # Because of invariance, List[subclass of X] is not a subclass - # of List[X], and ditto for MutableSequence. - self.assertNotIsSubclass(typing.List[Manager], typing.List[Employee]) - self.assertNotIsSubclass(typing.MutableSequence[Manager], - typing.MutableSequence[Employee]) - # It's still reflexive. - self.assertIsSubclass(typing.List[Employee], typing.List[Employee]) - self.assertIsSubclass(typing.MutableSequence[Employee], - typing.MutableSequence[Employee]) - - def test_covariance_tuple(self): - # Check covariace for Tuple (which are really special cases). - self.assertIsSubclass(Tuple[Manager], Tuple[Employee]) - self.assertNotIsSubclass(Tuple[Employee], Tuple[Manager]) - # And pairwise. - self.assertIsSubclass(Tuple[Manager, Manager], - Tuple[Employee, Employee]) - self.assertNotIsSubclass(Tuple[Employee, Employee], - Tuple[Manager, Employee]) - # And using ellipsis. - self.assertIsSubclass(Tuple[Manager, ...], Tuple[Employee, ...]) - self.assertNotIsSubclass(Tuple[Employee, ...], Tuple[Manager, ...]) - - def test_covariance_sequence(self): - # Check covariance for Sequence (which is just a generic class - # for this purpose, but using a type variable with covariant=True). - self.assertIsSubclass(typing.Sequence[Manager], - typing.Sequence[Employee]) - self.assertNotIsSubclass(typing.Sequence[Employee], - typing.Sequence[Manager]) - - def test_covariance_mapping(self): - # Ditto for Mapping (covariant in the value, invariant in the key). - self.assertIsSubclass(typing.Mapping[Employee, Manager], - typing.Mapping[Employee, Employee]) - self.assertNotIsSubclass(typing.Mapping[Manager, Employee], - typing.Mapping[Employee, Employee]) - self.assertNotIsSubclass(typing.Mapping[Employee, Manager], - typing.Mapping[Manager, Manager]) - self.assertNotIsSubclass(typing.Mapping[Manager, Employee], - typing.Mapping[Manager, Manager]) - - class CastTests(BaseTestCase): def test_basics(self): @@ -1247,7 +1163,6 @@ def test_iterable(self): # path and could fail. So call this a few times. self.assertIsInstance([], typing.Iterable) self.assertIsInstance([], typing.Iterable) - self.assertIsInstance([], typing.Iterable[int]) self.assertNotIsInstance(42, typing.Iterable) # Just in case, also test issubclass() a few times. self.assertIsSubclass(list, typing.Iterable) @@ -1256,7 +1171,6 @@ def test_iterable(self): def test_iterator(self): it = iter([]) self.assertIsInstance(it, typing.Iterator) - self.assertIsInstance(it, typing.Iterator[int]) self.assertNotIsInstance(42, typing.Iterator) @skipUnless(PY35, 'Python 3.5 required') @@ -1268,13 +1182,8 @@ def test_awaitable(self): globals(), ns) foo = ns['foo'] g = foo() - self.assertIsSubclass(type(g), typing.Awaitable[int]) self.assertIsInstance(g, typing.Awaitable) self.assertNotIsInstance(foo, typing.Awaitable) - self.assertIsSubclass(typing.Awaitable[Manager], - typing.Awaitable[Employee]) - self.assertNotIsSubclass(typing.Awaitable[Employee], - typing.Awaitable[Manager]) g.send(None) # Run foo() till completion, to avoid warning. @skipUnless(PY35, 'Python 3.5 required') @@ -1283,8 +1192,6 @@ def test_async_iterable(self): it = AsyncIteratorWrapper(base_it) self.assertIsInstance(it, typing.AsyncIterable) self.assertIsInstance(it, typing.AsyncIterable) - self.assertIsSubclass(typing.AsyncIterable[Manager], - typing.AsyncIterable[Employee]) self.assertNotIsInstance(42, typing.AsyncIterable) @skipUnless(PY35, 'Python 3.5 required') @@ -1292,8 +1199,6 @@ def test_async_iterator(self): base_it = range(10) # type: Iterator[int] it = AsyncIteratorWrapper(base_it) self.assertIsInstance(it, typing.AsyncIterator) - self.assertIsSubclass(typing.AsyncIterator[Manager], - typing.AsyncIterator[Employee]) self.assertNotIsInstance(42, typing.AsyncIterator) def test_sized(self): @@ -1457,10 +1362,6 @@ def foo(): yield 42 g = foo() self.assertIsSubclass(type(g), typing.Generator) - self.assertIsSubclass(typing.Generator[Manager, Employee, Manager], - typing.Generator[Employee, Manager, Employee]) - self.assertNotIsSubclass(typing.Generator[Manager, Manager, Manager], - typing.Generator[Employee, Employee, Employee]) def test_no_generator_instantiation(self): with self.assertRaises(TypeError): @@ -1511,7 +1412,6 @@ def manager(): cm = manager() self.assertIsInstance(cm, typing.ContextManager) - self.assertIsInstance(cm, typing.ContextManager[int]) self.assertNotIsInstance(42, typing.ContextManager) @@ -1653,22 +1553,16 @@ def test_basics(self): pat = re.compile('[a-z]+', re.I) self.assertIsSubclass(pat.__class__, Pattern) self.assertIsSubclass(type(pat), Pattern) - self.assertIsSubclass(type(pat), Pattern[str]) + self.assertIsInstance(pat, Pattern) mat = pat.search('12345abcde.....') self.assertIsSubclass(mat.__class__, Match) - self.assertIsSubclass(mat.__class__, Match[str]) - self.assertIsSubclass(mat.__class__, Match[bytes]) # Sad but true. self.assertIsSubclass(type(mat), Match) - self.assertIsSubclass(type(mat), Match[str]) + self.assertIsInstance(mat, Match) + # these should just work p = Pattern[Union[str, bytes]] - self.assertIsSubclass(Pattern[str], Pattern) - self.assertIsSubclass(Pattern[str], p) - m = Match[Union[bytes, str]] - self.assertIsSubclass(Match[bytes], Match) - self.assertIsSubclass(Match[bytes], m) def test_errors(self): with self.assertRaises(TypeError): @@ -1681,9 +1575,6 @@ def test_errors(self): with self.assertRaises(TypeError): # Too complicated? m[str] - with self.assertRaises(TypeError): - # We don't support isinstance(). - isinstance(42, Pattern) with self.assertRaises(TypeError): # We don't support isinstance(). isinstance(42, Pattern[str]) @@ -1710,7 +1601,7 @@ class A(typing.Match): pass self.assertEqual(str(ex.exception), - "A type alias cannot be subclassed") + "Cannot subclass typing._TypeAlias") class AllTests(BaseTestCase): diff --git a/src/typing.py b/src/typing.py index 4676d28c..925d9e42 100644 --- a/src/typing.py +++ b/src/typing.py @@ -89,6 +89,13 @@ def _qualname(x): return x.__name__ +def _trim_name(nm): + if nm.startswith('_') and nm not in ('_TypeAlias', + '_ForwardRef', '_TypingBase', '_FinalTypingBase'): + nm = nm[1:] + return nm + + class TypingMeta(type): """Metaclass for every type defined below. @@ -127,22 +134,69 @@ def _get_type_vars(self, tvars): pass def __repr__(self): - return '%s.%s' % (self.__module__, _qualname(self)) + qname = _trim_name(_qualname(self)) + return '%s.%s' % (self.__module__, qname) + + +class _TypingBase(metaclass=TypingMeta, _root=True): + """Indicator of special typing constructs.""" + + __slots__ = () + + + def __init__(self, *args, **kwds): + pass + + def __new__(cls, *args, **kwds): + """Constructor. + + This only exists to give a better error message in case + someone tries to subclass a special typing object (not a good idea). + """ + if (len(args) == 3 and + isinstance(args[0], str) and + isinstance(args[1], tuple)): + # Close enough. + raise TypeError("Cannot subclass %r" % cls) + return object.__new__(cls) + + # Things that are not classes also need these. + def _eval_type(self, globalns, localns): + return self + + def _get_type_vars(self, tvars): + pass + + def __repr__(self): + cls = type(self) + qname = _trim_name(_qualname(cls)) + return '%s.%s' % (cls.__module__, qname) + def __call__(self, *args, **kwds): + raise TypeError("Cannot instantiate %r" % type(self)) -class Final: + +class _FinalTypingBase(_TypingBase, _root=True): """Mix-in class to prevent instantiation.""" __slots__ = () - def __new__(self, *args, **kwds): - raise TypeError("Cannot instantiate %r" % self.__class__) + def __new__(cls, *args, _root=False, **kwds): + self = super().__new__(cls, *args, **kwds) + if _root is True: + return self + raise TypeError("Cannot instantiate %r" % cls) -class _ForwardRef(TypingMeta): +class _ForwardRef(_TypingBase, _root=True): """Wrapper to hold a forward reference.""" - def __new__(cls, arg): + __slots__ = ('__forward_arg__', '__forward_code__', + '__forward_evaluated__', '__forward_value__', + '__forward_frame__') + + def __init__(self, arg): + super().__init__(arg) if not isinstance(arg, str): raise TypeError('ForwardRef must be a string -- got %r' % (arg,)) try: @@ -150,7 +204,6 @@ def __new__(cls, arg): except SyntaxError: raise SyntaxError('ForwardRef must be an expression -- got %r' % (arg,)) - self = super().__new__(cls, arg, (), {}, _root=True) self.__forward_arg__ = arg self.__forward_code__ = code self.__forward_evaluated__ = False @@ -161,7 +214,6 @@ def __new__(cls, arg): frame = frame.f_back assert frame is not None self.__forward_frame__ = frame - return self def _eval_type(self, globalns, localns): if not self.__forward_evaluated__: @@ -177,49 +229,36 @@ def _eval_type(self, globalns, localns): self.__forward_evaluated__ = True return self.__forward_value__ + def __eq__(self, other): + if not isinstance(other, _ForwardRef): + return NotImplemented + return (self.__forward_arg__ == other.__forward_arg__ and + self.__forward_frame__ == other.__forward_frame__) + + def __hash__(self): + return hash((self.__forward_arg__, self.__forward_frame__)) + def __instancecheck__(self, obj): raise TypeError("Forward references cannot be used with isinstance().") def __subclasscheck__(self, cls): - if not self.__forward_evaluated__: - globalns = self.__forward_frame__.f_globals - localns = self.__forward_frame__.f_locals - try: - self._eval_type(globalns, localns) - except NameError: - return False # Too early. - return issubclass(cls, self.__forward_value__) + raise TypeError("Forward references cannot be used with issubclass().") def __repr__(self): return '_ForwardRef(%r)' % (self.__forward_arg__,) -class _TypeAlias: +class _TypeAlias(_TypingBase, _root=True): """Internal helper class for defining generic variants of concrete types. - Note that this is not a type; let's call it a pseudo-type. It can - be used in instance and subclass checks, e.g. isinstance(m, Match) - or issubclass(type(m), Match). However, it cannot be itself the - target of an issubclass() call; e.g. issubclass(Match, C) (for - some arbitrary class C) raises TypeError rather than returning - False. + Note that this is not a type; let's call it a pseudo-type. It cannot + be used in instance and subclass checks in parameterized form, i.e. + ``isinstance(42, Match[str])`` raises ``TypeError`` instead of returning + ``False``. """ __slots__ = ('name', 'type_var', 'impl_type', 'type_checker') - def __new__(cls, *args, **kwds): - """Constructor. - - This only exists to give a better error message in case - someone tries to subclass a type alias (not a good idea). - """ - if (len(args) == 3 and - isinstance(args[0], str) and - isinstance(args[1], tuple)): - # Close enough. - raise TypeError("A type alias cannot be subclassed") - return object.__new__(cls) - def __init__(self, name, type_var, impl_type, type_checker): """Initializer. @@ -232,9 +271,9 @@ def __init__(self, name, type_var, impl_type, type_checker): and returns a value that should be a type_var instance. """ assert isinstance(name, str), repr(name) - assert isinstance(type_var, type), repr(type_var) assert isinstance(impl_type, type), repr(impl_type) assert not isinstance(impl_type, TypingMeta), repr(impl_type) + assert isinstance(type_var, (type, _TypingBase)) self.name = name self.type_var = type_var self.impl_type = impl_type @@ -244,36 +283,41 @@ def __repr__(self): return "%s[%s]" % (self.name, _type_repr(self.type_var)) def __getitem__(self, parameter): - assert isinstance(parameter, type), repr(parameter) if not isinstance(self.type_var, TypeVar): raise TypeError("%s cannot be further parameterized." % self) - if self.type_var.__constraints__: - if not issubclass(parameter, Union[self.type_var.__constraints__]): + if self.type_var.__constraints__ and isinstance(parameter, type): + if not issubclass(parameter, self.type_var.__constraints__): raise TypeError("%s is not a valid substitution for %s." % (parameter, self.type_var)) + if isinstance(parameter, TypeVar): + raise TypeError("%s cannot be re-parameterized." % self.type_var) return self.__class__(self.name, parameter, self.impl_type, self.type_checker) + def __eq__(self, other): + if not isinstance(other, _TypeAlias): + return NotImplemented + return self.name == other.name and self.type_var == other.type_var + + def __hash__(self): + return hash((self.name, self.type_var)) + def __instancecheck__(self, obj): - raise TypeError("Type aliases cannot be used with isinstance().") + if not isinstance(self.type_var, TypeVar): + raise TypeError("Parameterized type aliases cannot be used " + "with isinstance().") + return isinstance(obj, self.impl_type) def __subclasscheck__(self, cls): - if cls is Any: - return True - if isinstance(cls, _TypeAlias): - # Covariance. For now, we compare by name. - return (cls.name == self.name and - issubclass(cls.type_var, self.type_var)) - else: - # Note that this is too lenient, because the - # implementation type doesn't carry information about - # whether it is about bytes or str (for example). - return issubclass(cls, self.impl_type) + if not isinstance(self.type_var, TypeVar): + raise TypeError("Parameterized type aliases cannot be used " + "with issubclass().") + return issubclass(cls, self.impl_type) def _get_type_vars(types, tvars): for t in types: - if isinstance(t, TypingMeta) or isinstance(t, _ClassVar): + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): t._get_type_vars(tvars) @@ -284,7 +328,7 @@ def _type_vars(types): def _eval_type(t, globalns, localns): - if isinstance(t, TypingMeta) or isinstance(t, _ClassVar): + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): return t._eval_type(globalns, localns) else: return t @@ -306,7 +350,7 @@ def _type_check(arg, msg): return type(None) if isinstance(arg, str): arg = _ForwardRef(arg) - if not isinstance(arg, (type, _TypeAlias)) and not callable(arg): + if not isinstance(arg, (type, _TypingBase)) and not callable(arg): raise TypeError(msg + " Got %.100r." % (arg,)) return arg @@ -328,23 +372,7 @@ def _type_repr(obj): return repr(obj) -class AnyMeta(TypingMeta): - """Metaclass for Any.""" - - def __new__(cls, name, bases, namespace, _root=False): - self = super().__new__(cls, name, bases, namespace, _root=_root) - return self - - def __instancecheck__(self, obj): - raise TypeError("Any cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - if not isinstance(cls, type): - return super().__subclasscheck__(cls) # To TypeError. - return True - - -class Any(Final, metaclass=AnyMeta, _root=True): +class _Any(_FinalTypingBase, _root=True): """Special type indicating an unconstrained type. - Any object is an instance of Any. @@ -354,8 +382,17 @@ class Any(Final, metaclass=AnyMeta, _root=True): __slots__ = () + def __instancecheck__(self, obj): + raise TypeError("Any cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Any cannot be used with issubclass().") + + +Any = _Any(_root=True) -class TypeVar(TypingMeta, metaclass=TypingMeta, _root=True): + +class TypeVar(_TypingBase, _root=True): """Type variable. Usage:: @@ -400,9 +437,14 @@ def longest(x: A, y: A) -> A: A.__constraints__ == (str, bytes) """ - def __new__(cls, name, *constraints, bound=None, + __slots__ = ('__name__', '__bound__', '__constraints__', + '__covariant__', '__contravariant__') + + def __init__(self, name, *constraints, bound=None, covariant=False, contravariant=False): - self = super().__new__(cls, name, (Final,), {}, _root=True) + super().__init__(name, *constraints, bound=bound, + covariant=covariant, contravariant=contravariant) + self.__name__ = name if covariant and contravariant: raise ValueError("Bivariant types are not supported.") self.__covariant__ = bool(covariant) @@ -417,7 +459,6 @@ def __new__(cls, name, *constraints, bound=None, self.__bound__ = _type_check(bound, "Bound must be a type.") else: self.__bound__ = None - return self def _get_type_vars(self, tvars): if self not in tvars: @@ -436,16 +477,7 @@ def __instancecheck__(self, instance): raise TypeError("Type variables cannot be used with isinstance().") def __subclasscheck__(self, cls): - # TODO: Make this raise TypeError too? - if cls is self: - return True - if cls is Any: - return True - if self.__bound__ is not None: - return issubclass(cls, self.__bound__) - if self.__constraints__: - return any(issubclass(cls, c) for c in self.__constraints__) - return True + raise TypeError("Type variables cannot be used with issubclass().") # Some unconstrained type variables. These are used by the container types. @@ -463,19 +495,85 @@ def __subclasscheck__(self, cls): AnyStr = TypeVar('AnyStr', bytes, str) -class UnionMeta(TypingMeta): - """Metaclass for Union.""" +def _tp_cache(func): + cached = functools.lru_cache()(func) + @functools.wraps(func) + def inner(*args, **kwds): + try: + return cached(*args, **kwds) + except TypeError: + pass # Do not duplicate real errors. + return func(*args, **kwds) + return inner + + +class _Union(_FinalTypingBase, _root=True): + """Union type; Union[X, Y] means either X or Y. + + To define a union, use e.g. Union[int, str]. Details: + + - The arguments must be types and there must be at least one. + + - None as an argument is a special case and is replaced by + type(None). + + - Unions of unions are flattened, e.g.:: + + Union[Union[int, str], float] == Union[int, str, float] + + - Unions of a single argument vanish, e.g.:: + + Union[int] == int # The constructor actually returns int + + - Redundant arguments are skipped, e.g.:: + + Union[int, str, int] == Union[int, str] + + - When comparing unions, the argument order is ignored, e.g.:: - def __new__(cls, name, bases, namespace, parameters=None, _root=False): + Union[int, str] == Union[str, int] + + - When two arguments have a subclass relationship, the least + derived argument is kept, e.g.:: + + class Employee: pass + class Manager(Employee): pass + Union[int, Employee, Manager] == Union[int, Employee] + Union[Manager, int, Employee] == Union[int, Employee] + Union[Employee, Manager] == Employee + + - Corollary: if Any is present it is the sole survivor, e.g.:: + + Union[int, Any] == Any + + - Similar for object:: + + Union[int, object] == object + + - To cut a tie: Union[object, Any] == Union[Any, object] == Any. + + - You cannot subclass or instantiate a union. + + - You cannot write Union[X][Y] (what would it mean?). + + - You can use Optional[X] as a shorthand for Union[X, None]. + """ + + __slots__ = ('__union_params__', '__union_set_params__') + + def __new__(cls, parameters=None, *args, _root=False): + self = super().__new__(cls, parameters, *args, _root=_root) if parameters is None: - return super().__new__(cls, name, bases, namespace, _root=_root) + self.__union_params__ = None + self.__union_set_params__ = None + return self if not isinstance(parameters, tuple): raise TypeError("Expected parameters=") # Flatten out Union[Union[...], ...] and type-check non-Union args. params = [] msg = "Union[arg, ...]: each arg must be a type." for p in parameters: - if isinstance(p, UnionMeta): + if isinstance(p, _Union): params.extend(p.__union_params__) else: params.append(_type_check(p, msg)) @@ -499,22 +597,16 @@ def __new__(cls, name, bases, namespace, parameters=None, _root=False): for t1 in params: if t1 is Any: return Any - if isinstance(t1, TypeVar): - continue - if isinstance(t1, _TypeAlias): - # _TypeAlias is not a real class. - continue if not isinstance(t1, type): - assert callable(t1) # A callable might sneak through. continue if any(isinstance(t2, type) and issubclass(t1, t2) - for t2 in all_params - {t1} if not isinstance(t2, TypeVar)): + for t2 in all_params - {t1} + if not (isinstance(t2, GenericMeta) and + t2.__origin__ is not None)): all_params.remove(t1) # It's not a union if there's only one type left. if len(all_params) == 1: return all_params.pop() - # Create a new class with these params. - self = super().__new__(cls, name, bases, {}, _root=True) self.__union_params__ = tuple(t for t in params if t in all_params) self.__union_set_params__ = frozenset(self.__union_params__) return self @@ -525,8 +617,7 @@ def _eval_type(self, globalns, localns): if p == self.__union_params__: return self else: - return self.__class__(self.__name__, self.__bases__, {}, - p, _root=True) + return self.__class__(p, _root=True) def _get_type_vars(self, tvars): if self.__union_params__: @@ -539,6 +630,7 @@ def __repr__(self): for t in self.__union_params__)) return r + @_tp_cache def __getitem__(self, parameters): if self.__union_params__ is not None: raise TypeError( @@ -547,11 +639,10 @@ def __getitem__(self, parameters): raise TypeError("Cannot take a Union of no types.") if not isinstance(parameters, tuple): parameters = (parameters,) - return self.__class__(self.__name__, self.__bases__, - dict(self.__dict__), parameters, _root=True) + return self.__class__(parameters, _root=True) def __eq__(self, other): - if not isinstance(other, UnionMeta): + if not isinstance(other, _Union): return NotImplemented return self.__union_set_params__ == other.__union_set_params__ @@ -562,110 +653,45 @@ def __instancecheck__(self, obj): raise TypeError("Unions cannot be used with isinstance().") def __subclasscheck__(self, cls): - if cls is Any: - return True - if self.__union_params__ is None: - return isinstance(cls, UnionMeta) - elif isinstance(cls, UnionMeta): - if cls.__union_params__ is None: - return False - return all(issubclass(c, self) for c in (cls.__union_params__)) - elif isinstance(cls, TypeVar): - if cls in self.__union_params__: - return True - if cls.__constraints__: - return issubclass(Union[cls.__constraints__], self) - return False - else: - return any(issubclass(cls, t) for t in self.__union_params__) + raise TypeError("Unions cannot be used with issubclass().") -class Union(Final, metaclass=UnionMeta, _root=True): - """Union type; Union[X, Y] means either X or Y. - - To define a union, use e.g. Union[int, str]. Details: - - - The arguments must be types and there must be at least one. - - - None as an argument is a special case and is replaced by - type(None). - - - Unions of unions are flattened, e.g.:: - - Union[Union[int, str], float] == Union[int, str, float] - - - Unions of a single argument vanish, e.g.:: - - Union[int] == int # The constructor actually returns int - - - Redundant arguments are skipped, e.g.:: - - Union[int, str, int] == Union[int, str] - - - When comparing unions, the argument order is ignored, e.g.:: - - Union[int, str] == Union[str, int] - - - When two arguments have a subclass relationship, the least - derived argument is kept, e.g.:: - - class Employee: pass - class Manager(Employee): pass - Union[int, Employee, Manager] == Union[int, Employee] - Union[Manager, int, Employee] == Union[int, Employee] - Union[Employee, Manager] == Employee - - - Corollary: if Any is present it is the sole survivor, e.g.:: - - Union[int, Any] == Any +Union = _Union(_root=True) - - Similar for object:: - Union[int, object] == object - - - To cut a tie: Union[object, Any] == Union[Any, object] == Any. - - - You cannot subclass or instantiate a union. - - - You cannot write Union[X][Y] (what would it mean?). +class _Optional(_FinalTypingBase, _root=True): + """Optional type. - - You can use Optional[X] as a shorthand for Union[X, None]. + Optional[X] is equivalent to Union[X, type(None)]. """ - # Unsubscripted Union type has params set to None. - __union_params__ = None - __union_set_params__ = None - - -class OptionalMeta(TypingMeta): - """Metaclass for Optional.""" - - def __new__(cls, name, bases, namespace, _root=False): - return super().__new__(cls, name, bases, namespace, _root=_root) + __slots__ = () + @_tp_cache def __getitem__(self, arg): arg = _type_check(arg, "Optional[t] requires a single type.") return Union[arg, type(None)] -class Optional(Final, metaclass=OptionalMeta, _root=True): - """Optional type. +Optional = _Optional(_root=True) - Optional[X] is equivalent to Union[X, type(None)]. - """ - __slots__ = () +class _Tuple(_FinalTypingBase, _root=True): + """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. + Example: Tuple[T1, T2] is a tuple of two elements corresponding + to type variables T1 and T2. Tuple[int, float, str] is a tuple + of an int, a float and a string. + + To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. + """ -class TupleMeta(TypingMeta): - """Metaclass for Tuple.""" + __slots__ = ('__tuple_params__', '__tuple_use_ellipsis__') - def __new__(cls, name, bases, namespace, parameters=None, + def __init__(self, parameters=None, use_ellipsis=False, _root=False): - self = super().__new__(cls, name, bases, namespace, _root=_root) self.__tuple_params__ = parameters self.__tuple_use_ellipsis__ = use_ellipsis - return self def _get_type_vars(self, tvars): if self.__tuple_params__: @@ -679,8 +705,7 @@ def _eval_type(self, globalns, localns): if p == self.__tuple_params__: return self else: - return self.__class__(self.__name__, self.__bases__, {}, - p, _root=True) + return self.__class__(p, _root=True) def __repr__(self): r = super().__repr__() @@ -694,6 +719,7 @@ def __repr__(self): ', '.join(params)) return r + @_tp_cache def __getitem__(self, parameters): if self.__tuple_params__ is not None: raise TypeError("Cannot re-parameterize %r" % (self,)) @@ -707,64 +733,50 @@ def __getitem__(self, parameters): use_ellipsis = False msg = "Tuple[t0, t1, ...]: each t must be a type." parameters = tuple(_type_check(p, msg) for p in parameters) - return self.__class__(self.__name__, self.__bases__, - dict(self.__dict__), parameters, + return self.__class__(parameters, use_ellipsis=use_ellipsis, _root=True) def __eq__(self, other): - if not isinstance(other, TupleMeta): + if not isinstance(other, _Tuple): return NotImplemented return (self.__tuple_params__ == other.__tuple_params__ and self.__tuple_use_ellipsis__ == other.__tuple_use_ellipsis__) def __hash__(self): - return hash(self.__tuple_params__) + return hash((self.__tuple_params__, self.__tuple_use_ellipsis__)) def __instancecheck__(self, obj): - raise TypeError("Tuples cannot be used with isinstance().") + if self.__tuple_params__ == None: + return isinstance(obj, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with isinstance().") def __subclasscheck__(self, cls): - if cls is Any: - return True - if not isinstance(cls, type): - return super().__subclasscheck__(cls) # To TypeError. - if issubclass(cls, tuple): - return True # Special case. - if not isinstance(cls, TupleMeta): - return super().__subclasscheck__(cls) # False. - if self.__tuple_params__ is None: - return True - if cls.__tuple_params__ is None: - return False # ??? - if cls.__tuple_use_ellipsis__ != self.__tuple_use_ellipsis__: - return False - # Covariance. - return (len(self.__tuple_params__) == len(cls.__tuple_params__) and - all(issubclass(x, p) - for x, p in zip(cls.__tuple_params__, - self.__tuple_params__))) - - -class Tuple(Final, metaclass=TupleMeta, _root=True): - """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. + if self.__tuple_params__ == None: + return issubclass(cls, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with issubclass().") - Example: Tuple[T1, T2] is a tuple of two elements corresponding - to type variables T1 and T2. Tuple[int, float, str] is a tuple - of an int, a float and a string. - To specify a variable-length tuple of homogeneous type, use Sequence[T]. - """ +Tuple = _Tuple(_root=True) - __slots__ = () +class _Callable(_FinalTypingBase, _root=True): + """Callable type; Callable[[int], str] is a function of (int) -> str. -class CallableMeta(TypingMeta): - """Metaclass for Callable.""" + The subscription syntax must always be used with exactly two + values: the argument list and the return type. The argument list + must be a list of types; the return type must be a single type. - def __new__(cls, name, bases, namespace, _root=False, - args=None, result=None): + There is no syntax to indicate optional or keyword arguments, + such function types are rarely used as callback types. + """ + + __slots__ = ('__args__', '__result__') + + def __init__(self, args=None, result=None, _root=False): if args is None and result is None: - pass # Must be 'class Callable'. + pass else: if args is not Ellipsis: if not isinstance(args, list): @@ -775,10 +787,8 @@ def __new__(cls, name, bases, namespace, _root=False, args = tuple(_type_check(arg, msg) for arg in args) msg = "Callable[args, result]: result must be a type." result = _type_check(result, msg) - self = super().__new__(cls, name, bases, namespace, _root=_root) self.__args__ = args self.__result__ = result - return self def _get_type_vars(self, tvars): if self.__args__ and self.__args__ is not Ellipsis: @@ -795,8 +805,7 @@ def _eval_type(self, globalns, localns): if args == self.__args__ and result == self.__result__: return self else: - return self.__class__(self.__name__, self.__bases__, {}, - args=args, result=result, _root=True) + return self.__class__(args, result, _root=True) def __repr__(self): r = super().__repr__() @@ -816,12 +825,10 @@ def __getitem__(self, parameters): raise TypeError( "Callable must be used as Callable[[arg, ...], result].") args, result = parameters - return self.__class__(self.__name__, self.__bases__, - dict(self.__dict__), _root=True, - args=args, result=result) + return self.__class__(args, result, _root=True) def __eq__(self, other): - if not isinstance(other, CallableMeta): + if not isinstance(other, _Callable): return NotImplemented return (self.__args__ == other.__args__ and self.__result__ == other.__result__) @@ -836,31 +843,18 @@ def __instancecheck__(self, obj): if self.__args__ is None and self.__result__ is None: return isinstance(obj, collections_abc.Callable) else: - raise TypeError("Callable[] cannot be used with isinstance().") + raise TypeError("Parameterized Callable cannot be used " + "with isinstance().") def __subclasscheck__(self, cls): - if cls is Any: - return True - if not isinstance(cls, CallableMeta): - return super().__subclasscheck__(cls) if self.__args__ is None and self.__result__ is None: - return True - # We're not doing covariance or contravariance -- this is *invariance*. - return self == cls - - -class Callable(Final, metaclass=CallableMeta, _root=True): - """Callable type; Callable[[int], str] is a function of (int) -> str. - - The subscription syntax must always be used with exactly two - values: the argument list and the return type. The argument list - must be a list of types; the return type must be a single type. + return issubclass(cls, collections_abc.Callable) + else: + raise TypeError("Parameterized Callable cannot be used " + "with issubclass().") - There is no syntax to indicate optional or keyword arguments, - such function types are rarely used as callback types. - """ - __slots__ = () +Callable = _Callable(_root=True) def _gorg(a): @@ -985,6 +979,7 @@ def __eq__(self, other): def __hash__(self): return hash((self.__name__, self.__parameters__)) + @_tp_cache def __getitem__(self, params): if not isinstance(params, tuple): params = (params,) @@ -1040,44 +1035,18 @@ def __instancecheck__(self, instance): return self.__subclasscheck__(instance.__class__) def __subclasscheck__(self, cls): - if cls is Any: - return True - if isinstance(cls, GenericMeta): - # For a covariant class C(Generic[T]), - # C[X] is a subclass of C[Y] iff X is a subclass of Y. - origin = self.__origin__ - if origin is not None and origin is cls.__origin__: - assert len(self.__args__) == len(origin.__parameters__) - assert len(cls.__args__) == len(origin.__parameters__) - for p_self, p_cls, p_origin in zip(self.__args__, - cls.__args__, - origin.__parameters__): - if isinstance(p_origin, TypeVar): - if p_origin.__covariant__: - # Covariant -- p_cls must be a subclass of p_self. - if not issubclass(p_cls, p_self): - break - elif p_origin.__contravariant__: - # Contravariant. I think it's the opposite. :-) - if not issubclass(p_self, p_cls): - break - else: - # Invariant -- p_cls and p_self must equal. - if p_self != p_cls: - break - else: - # If the origin's parameter is not a typevar, - # insist on invariance. - if p_self != p_cls: - break - else: - return True - # If we break out of the loop, the superclass gets a chance. + if self is Generic: + raise TypeError("Class %r cannot be used with class " + "or instance checks" % self) + if (self.__origin__ is not None and + sys._getframe(1).f_globals['__name__'] != 'abc'): + raise TypeError("Parameterized generics cannot be used with class " + "or instance checks") if super().__subclasscheck__(cls): return True - if self.__extra__ is None or isinstance(cls, GenericMeta): - return False - return issubclass(cls, self.__extra__) + if self.__extra__ is not None: + return issubclass(cls, self.__extra__) + return False # Prevent checks for Generic to crash when defining Generic. @@ -1117,7 +1086,7 @@ def __new__(cls, *args, **kwds): return obj -class _ClassVar(metaclass=TypingMeta, _root=True): +class _ClassVar(_FinalTypingBase, _root=True): """Special type construct to mark class variables. An annotation wrapped in ClassVar indicates that a given @@ -1134,36 +1103,35 @@ class Starship: be used with isinstance() or issubclass(). """ - def __init__(self, tp=None, _root=False): - cls = type(self) - if _root: - self.__type__ = tp - else: - raise TypeError('Cannot initialize {}'.format(cls.__name__[1:])) + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp def __getitem__(self, item): cls = type(self) if self.__type__ is None: return cls(_type_check(item, - '{} accepts only types.'.format(cls.__name__[1:])), + '{} accepts only single type.'.format(cls.__name__[1:])), _root=True) raise TypeError('{} cannot be further subscripted' .format(cls.__name__[1:])) def _eval_type(self, globalns, localns): - return type(self)(_eval_type(self.__type__, globalns, localns), - _root=True) + new_tp = _eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) def _get_type_vars(self, tvars): if self.__type__: _get_type_vars(self.__type__, tvars) def __repr__(self): - cls = type(self) - if not self.__type__: - return '{}.{}'.format(cls.__module__, cls.__name__[1:]) - return '{}.{}[{}]'.format(cls.__module__, cls.__name__[1:], - _type_repr(self.__type__)) + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(_type_repr(self.__type__)) + return r def __hash__(self): return hash((type(self).__name__, self.__type__)) @@ -1614,52 +1582,52 @@ class Collection(Sized, Iterable[T_co], Container[T_co], if hasattr(collections_abc, 'Collection'): class AbstractSet(Collection[T_co], extra=collections_abc.Set): - pass + __slots__ = () else: class AbstractSet(Sized, Iterable[T_co], Container[T_co], extra=collections_abc.Set): - pass + __slots__ = () class MutableSet(AbstractSet[T], extra=collections_abc.MutableSet): - pass + __slots__ = () # NOTE: It is only covariant in the value type. if hasattr(collections_abc, 'Collection'): class Mapping(Collection[KT], Generic[KT, VT_co], extra=collections_abc.Mapping): - pass + __slots__ = () else: class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co], extra=collections_abc.Mapping): - pass + __slots__ = () class MutableMapping(Mapping[KT, VT], extra=collections_abc.MutableMapping): - pass + __slots__ = () if hasattr(collections_abc, 'Reversible'): if hasattr(collections_abc, 'Collection'): class Sequence(Reversible[T_co], Collection[T_co], extra=collections_abc.Sequence): - pass + __slots__ = () else: class Sequence(Sized, Reversible[T_co], Container[T_co], extra=collections_abc.Sequence): - pass + __slots__ = () else: class Sequence(Sized, Iterable[T_co], Container[T_co], extra=collections_abc.Sequence): - pass + __slots__ = () class MutableSequence(Sequence[T], extra=collections_abc.MutableSequence): - pass + __slots__ = () class ByteString(Sequence[int], extra=collections_abc.ByteString): - pass + __slots__ = () ByteString.register(type(memoryview(b''))) @@ -1667,6 +1635,8 @@ class ByteString(Sequence[int], extra=collections_abc.ByteString): class List(list, MutableSequence[T], extra=list): + __slots__ = () + def __new__(cls, *args, **kwds): if _geqv(cls, List): raise TypeError("Type List cannot be instantiated; " @@ -1676,6 +1646,8 @@ def __new__(cls, *args, **kwds): class Set(set, MutableSet[T], extra=set): + __slots__ = () + def __new__(cls, *args, **kwds): if _geqv(cls, Set): raise TypeError("Type Set cannot be instantiated; " @@ -1683,22 +1655,7 @@ def __new__(cls, *args, **kwds): return set.__new__(cls, *args, **kwds) -class _FrozenSetMeta(GenericMeta): - """This metaclass ensures set is not a subclass of FrozenSet. - - Without this metaclass, set would be considered a subclass of - FrozenSet, because FrozenSet.__extra__ is collections.abc.Set, and - set is a subclass of that. - """ - - def __subclasscheck__(self, cls): - if issubclass(cls, Set): - return False - return super().__subclasscheck__(cls) - - -class FrozenSet(frozenset, AbstractSet[T_co], metaclass=_FrozenSetMeta, - extra=frozenset): +class FrozenSet(frozenset, AbstractSet[T_co], extra=frozenset): __slots__ = () def __new__(cls, *args, **kwds): @@ -1709,23 +1666,23 @@ def __new__(cls, *args, **kwds): class MappingView(Sized, Iterable[T_co], extra=collections_abc.MappingView): - pass + __slots__ = () class KeysView(MappingView[KT], AbstractSet[KT], extra=collections_abc.KeysView): - pass + __slots__ = () class ItemsView(MappingView[Tuple[KT, VT_co]], AbstractSet[Tuple[KT, VT_co]], Generic[KT, VT_co], extra=collections_abc.ItemsView): - pass + __slots__ = () class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView): - pass + __slots__ = () if hasattr(contextlib, 'AbstractContextManager'): @@ -1736,6 +1693,8 @@ class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager): class Dict(dict, MutableMapping[KT, VT], extra=dict): + __slots__ = () + def __new__(cls, *args, **kwds): if _geqv(cls, Dict): raise TypeError("Type Dict cannot be instantiated; " @@ -1745,6 +1704,8 @@ def __new__(cls, *args, **kwds): class DefaultDict(collections.defaultdict, MutableMapping[KT, VT], extra=collections.defaultdict): + __slots__ = () + def __new__(cls, *args, **kwds): if _geqv(cls, DefaultDict): raise TypeError("Type DefaultDict cannot be instantiated; " @@ -1800,6 +1761,8 @@ def new_user(user_class: Type[U]) -> U: At this point the type checker knows that joe has type BasicUser. """ + __slots__ = () + def _make_nmtuple(name, types): nm_tpl = collections.namedtuple(name, [n for n, t in types])