From 7d73596a745882f7d4cd65749988f09f191b02b1 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Sep 2016 14:51:56 +0200 Subject: [PATCH 01/27] Started #136 --- src/test_typing.py | 104 ++++++------------------------------------ src/typing.py | 110 +++++++++++++++++---------------------------- 2 files changed, 53 insertions(+), 161 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index 3b99060f..b9497ff1 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -62,18 +62,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): + self.assertTrue(issubclass(Employee, Any)) + with self.assertRaises(TypeError): + self.assertFalse(issubclass(Any, Employee)) def test_repr(self): self.assertEqual(repr(Any), 'typing.Any') @@ -97,23 +90,7 @@ 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 +101,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 +111,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 +153,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 +167,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) @@ -357,29 +306,10 @@ def Elem(*args): 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): @@ -1653,22 +1583,14 @@ 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]) 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]) + # 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): diff --git a/src/typing.py b/src/typing.py index 4676d28c..01e7b66d 100644 --- a/src/typing.py +++ b/src/typing.py @@ -130,19 +130,25 @@ def __repr__(self): return '%s.%s' % (self.__module__, _qualname(self)) -class Final: +class TypingBase: + """Indicator of special typing constructs.""" + + +class Final(TypingBase): """Mix-in class to prevent instantiation.""" __slots__ = () - def __new__(self, *args, **kwds): + def __new__(self, *args, _root=False, **kwds): + if _root: + return object.__new__(self) raise TypeError("Cannot instantiate %r" % self.__class__) -class _ForwardRef(TypingMeta): +class _ForwardRef(TypingBase): """Wrapper to hold a forward reference.""" - def __new__(cls, arg): + def __init__(self, arg): if not isinstance(arg, str): raise TypeError('ForwardRef must be a string -- got %r' % (arg,)) try: @@ -150,7 +156,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 +166,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__: @@ -181,20 +185,13 @@ 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, metaclass=TypingMeta, _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 @@ -232,11 +229,10 @@ 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) self.name = name - self.type_var = type_var + self.type_var = _type_check(type_var, "Type alias accepts only types") self.impl_type = impl_type self.type_checker = type_checker @@ -244,7 +240,6 @@ 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__: @@ -258,22 +253,14 @@ def __instancecheck__(self, obj): raise TypeError("Type aliases cannot be used with isinstance().") 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("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 +271,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 +293,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,34 +315,29 @@ 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 +class _Any(Final, metaclass=TypingMeta, _root=True): 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 + raise TypeError("Any cannot be used with issubclass().") + def _eval_type(self, globalns, localns): + return self -class Any(Final, metaclass=AnyMeta, _root=True): - """Special type indicating an unconstrained type. + def _get_type_vars(self, tvars): + pass - - 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. - """ + def __repr__(self): + cls = type(self) + return '{}.{}'.format(cls.__module__, cls.__name__[1:]) - __slots__ = () + +Any = _Any(_root=True) -class TypeVar(TypingMeta, metaclass=TypingMeta, _root=True): +class TypeVar(TypingBase, metaclass=TypingMeta, _root=True): """Type variable. Usage:: @@ -400,9 +382,9 @@ def longest(x: A, y: A) -> A: A.__constraints__ == (str, bytes) """ - def __new__(cls, name, *constraints, bound=None, + def __init__(self, name, *constraints, bound=None, covariant=False, contravariant=False): - self = super().__new__(cls, name, (Final,), {}, _root=True) + self.__name__ = name if covariant and contravariant: raise ValueError("Bivariant types are not supported.") self.__covariant__ = bool(covariant) @@ -417,12 +399,14 @@ 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: tvars.append(self) + def _eval_type(self, globalns, localns): + return self + def __repr__(self): if self.__covariant__: prefix = '+' @@ -436,16 +420,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. @@ -505,7 +480,6 @@ def __new__(cls, name, bases, namespace, parameters=None, _root=False): # _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)): @@ -1117,7 +1091,7 @@ def __new__(cls, *args, **kwds): return obj -class _ClassVar(metaclass=TypingMeta, _root=True): +class _ClassVar(Final, metaclass=TypingMeta, _root=True): """Special type construct to mark class variables. An annotation wrapped in ClassVar indicates that a given @@ -1134,12 +1108,8 @@ 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:])) + def __init__(self, tp=None, **kwds): + self.__type__ = tp def __getitem__(self, item): cls = type(self) From 20de824f97409417d5c16673c044e8c6a027e2a1 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Sep 2016 15:01:38 +0200 Subject: [PATCH 02/27] Started #136 --- src/typing.py | 145 ++++++++++++++++++++++---------------------------- 1 file changed, 64 insertions(+), 81 deletions(-) diff --git a/src/typing.py b/src/typing.py index 01e7b66d..620c423b 100644 --- a/src/typing.py +++ b/src/typing.py @@ -316,6 +316,12 @@ def _type_repr(obj): class _Any(Final, metaclass=TypingMeta, _root=True): + """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. + """ def __instancecheck__(self, obj): raise TypeError("Any cannot be used with isinstance().") @@ -438,12 +444,64 @@ def __subclasscheck__(self, cls): AnyStr = TypeVar('AnyStr', bytes, str) -class UnionMeta(TypingMeta): - """Metaclass for Union.""" +class _Union(Final, metaclass=TypingMeta, _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.:: - def __new__(cls, name, bases, namespace, parameters=None, _root=False): + 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]. + """ + + def __new__(cls, parameters=None, _root=False): + self = object.__new__(cls) 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. @@ -474,11 +532,6 @@ 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): continue if any(isinstance(t2, type) and issubclass(t1, t2) @@ -488,7 +541,6 @@ def __new__(cls, name, bases, namespace, parameters=None, _root=False): 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 @@ -536,79 +588,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__) - - -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.:: + raise TypeError("Unions cannot be used with issubclass().") - 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]. - """ - # Unsubscripted Union type has params set to None. - __union_params__ = None - __union_set_params__ = None +Union(Final, metaclass=UnionMeta, _root=True): class OptionalMeta(TypingMeta): From 8e6f0a689a8f7871593d74a5ec32068e6c891c58 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Sep 2016 17:08:14 +0200 Subject: [PATCH 03/27] More work on #136 --- src/test_typing.py | 101 +++++++--------------------- src/typing.py | 160 ++++++++++++++++++--------------------------- 2 files changed, 87 insertions(+), 174 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index b9497ff1..840702bc 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -179,8 +179,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] @@ -209,18 +217,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) @@ -233,13 +229,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] @@ -276,10 +265,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]) @@ -304,24 +289,19 @@ def Elem(*args): Union[Elem, str] # Nor should this -class TypeVarUnionTests(BaseTestCase): - - def test_var_union_subclass(self): - self.assertTrue(issubclass(T, Union[int, T])) - self.assertTrue(issubclass(KT, Union[KT, VT])) - - 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]) @@ -337,21 +317,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') @@ -369,17 +335,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]) @@ -808,19 +766,6 @@ def test_invariance(self): 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). diff --git a/src/typing.py b/src/typing.py index 620c423b..5b9dbed2 100644 --- a/src/typing.py +++ b/src/typing.py @@ -142,7 +142,7 @@ class Final(TypingBase): def __new__(self, *args, _root=False, **kwds): if _root: return object.__new__(self) - raise TypeError("Cannot instantiate %r" % self.__class__) + raise TypeError("Cannot instantiate %r" % self) class _ForwardRef(TypingBase): @@ -242,10 +242,13 @@ def __repr__(self): def __getitem__(self, 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) and parameter != self.type_var: + raise TypeError("%s is not a valid substitution for %s." % + (parameter, self.type_var)) return self.__class__(self.name, parameter, self.impl_type, self.type_checker) @@ -445,7 +448,7 @@ def __subclasscheck__(self, cls): class _Union(Final, metaclass=TypingMeta, _root=True): - """Union type; Union[X, Y] means either X or Y. + """Union type; Union[X, Y] means either X or Y. To define a union, use e.g. Union[int, str]. Details: @@ -497,7 +500,7 @@ class Manager(Employee): pass """ def __new__(cls, parameters=None, _root=False): - self = object.__new__(cls) + self = super().__new__(cls, _root=_root) if parameters is None: self.__union_params__ = None self.__union_set_params__ = None @@ -508,7 +511,7 @@ def __new__(cls, parameters=None, _root=False): 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)) @@ -551,15 +554,15 @@ 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__: _get_type_vars(self.__union_params__, tvars) def __repr__(self): - r = super().__repr__() + cls = type(self) + r = '{}.{}'.format(cls.__module__, cls.__name__[1:]) if self.__union_params__: r += '[%s]' % (', '.join(_type_repr(t) for t in self.__union_params__)) @@ -573,11 +576,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__ @@ -591,38 +593,41 @@ def __subclasscheck__(self, cls): raise TypeError("Unions cannot be used with issubclass().") -Union(Final, metaclass=UnionMeta, _root=True): +Union = _Union(_root=True) -class OptionalMeta(TypingMeta): - """Metaclass for Optional.""" +class _Optional(Final, metaclass=TypingMeta, _root=True): + """Optional type. - def __new__(cls, name, bases, namespace, _root=False): - return super().__new__(cls, name, bases, namespace, _root=_root) + Optional[X] is equivalent to Union[X, type(None)]. + """ + + def __repr__(self): + cls = type(self) + return '{}.{}'.format(cls.__module__, cls.__name__[1:]) 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(Final, metaclass=TypingMeta, _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. -class TupleMeta(TypingMeta): - """Metaclass for Tuple.""" + To specify a variable-length tuple of homogeneous type, use Sequence[T]. + """ - 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__: @@ -636,11 +641,11 @@ 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__() + cls = type(self) + r = '{}.{}'.format(cls.__module__, cls.__name__[1:]) if self.__tuple_params__ is not None: params = [_type_repr(p) for p in self.__tuple_params__] if self.__tuple_use_ellipsis__: @@ -664,12 +669,11 @@ 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__) @@ -678,50 +682,33 @@ 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): - 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__))) + if self.__tuple_params__ == None: + return issubclass(cls, tuple) + raise TypeError("Parameterized Tuple cannot be used with issubclass().") -class Tuple(Final, metaclass=TupleMeta, _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. +Tuple = _Tuple(_root=True) - To specify a variable-length tuple of homogeneous type, use Sequence[T]. - """ - __slots__ = () +class _Callable(Final, metaclass=TypingMeta, _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. -class CallableMeta(TypingMeta): - """Metaclass for Callable.""" + There is no syntax to indicate optional or keyword arguments, + such function types are rarely used as callback types. + """ - def __new__(cls, name, bases, namespace, _root=False, - args=None, result=None): + 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): @@ -732,10 +719,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: @@ -752,11 +737,11 @@ 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__() + cls = type(self) + r = '{}.{}'.format(cls.__module__, cls.__name__[1:]) if self.__args__ is not None or self.__result__ is not None: if self.__args__ is Ellipsis: args_r = '...' @@ -773,12 +758,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__) @@ -793,31 +776,16 @@ 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): From bcde4c6f8bfbf815f0dfb217d5fe59eba806d8eb Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Sep 2016 21:05:34 +0200 Subject: [PATCH 04/27] Finish first round of #136 --- src/test_typing.py | 45 ++++++--------------------------------------- src/typing.py | 43 ++++++------------------------------------- 2 files changed, 12 insertions(+), 76 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index 840702bc..5207c86f 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -504,6 +504,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') @@ -753,39 +759,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_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): @@ -1122,7 +1095,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) @@ -1131,7 +1103,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') @@ -1332,10 +1303,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): diff --git a/src/typing.py b/src/typing.py index 5b9dbed2..8aa5dbaf 100644 --- a/src/typing.py +++ b/src/typing.py @@ -538,7 +538,7 @@ def __new__(cls, parameters=None, _root=False): if not isinstance(t1, type): 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: @@ -965,44 +965,13 @@ 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.__origin__ is not None and sys._getframe(1).f_globals['__name__'] != 'abc': + raise TypeError('Parameterized Generic could not 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. From 9987bb2ae5a17c05361fff5d040a7968d1a1b41e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Sep 2016 21:15:47 +0200 Subject: [PATCH 05/27] Shorter error messages --- src/typing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/typing.py b/src/typing.py index 8aa5dbaf..60385a3f 100644 --- a/src/typing.py +++ b/src/typing.py @@ -965,8 +965,10 @@ def __instancecheck__(self, instance): return self.__subclasscheck__(instance.__class__) def __subclasscheck__(self, cls): + if self is Generic: + raise TypeError("Class %r can't 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 Generic could not be used with class or instance checks') + raise TypeError("Parameterized generics can't be used with class or instance checks") if super().__subclasscheck__(cls): return True if self.__extra__ is not None: From 90d72da6f2cd79970d5205d4faaaf644ce68bdfb Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Sep 2016 21:34:57 +0200 Subject: [PATCH 06/27] Added lru_cache for Generic[] --- src/typing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/typing.py b/src/typing.py index 60385a3f..99476664 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1,5 +1,6 @@ import abc from abc import abstractmethod, abstractproperty +from functools import lru_cache import collections import contextlib import functools @@ -910,6 +911,7 @@ def __eq__(self, other): def __hash__(self): return hash((self.__name__, self.__parameters__)) + @lru_cache(maxsize=25) def __getitem__(self, params): if not isinstance(params, tuple): params = (params,) From 603ff6d8e6285f9aeb4e49df1e7d0ccd46a35294 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Sep 2016 21:50:29 +0200 Subject: [PATCH 07/27] Flexible cache for Generic[] --- src/typing.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/typing.py b/src/typing.py index 99476664..17168a5e 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1,6 +1,6 @@ import abc from abc import abstractmethod, abstractproperty -from functools import lru_cache +from functools import lru_cache, wraps import collections import contextlib import functools @@ -826,6 +826,18 @@ def _next_in_mro(cls): return next_in_mro +def tp_cache(func): + cached = lru_cache(maxsize=25)(func) + wraps(func) + def inner(*args, **kwargs): + if any(not isinstance(arg, collections_abc.Hashable) for arg in args): + print("Not Hashable") + return func(*args, **kwargs) + else: + return cached(*args, **kwargs) + return inner + + class GenericMeta(TypingMeta, abc.ABCMeta): """Metaclass for generic types.""" @@ -911,7 +923,7 @@ def __eq__(self, other): def __hash__(self): return hash((self.__name__, self.__parameters__)) - @lru_cache(maxsize=25) + @tp_cache def __getitem__(self, params): if not isinstance(params, tuple): params = (params,) From fa4a681d8624d75bd71816b3473d9e85a526513d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Sep 2016 22:46:18 +0200 Subject: [PATCH 08/27] Skip GenericMeta in FrozenSetMeta MRO --- src/typing.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/typing.py b/src/typing.py index 17168a5e..a0d884a1 100644 --- a/src/typing.py +++ b/src/typing.py @@ -844,7 +844,6 @@ class GenericMeta(TypingMeta, abc.ABCMeta): def __new__(cls, name, bases, namespace, tvars=None, args=None, origin=None, extra=None): self = super().__new__(cls, name, bases, namespace, _root=True) - if tvars is not None: # Called from __getitem__() below. assert origin is not None @@ -1600,7 +1599,7 @@ class _FrozenSetMeta(GenericMeta): def __subclasscheck__(self, cls): if issubclass(cls, Set): return False - return super().__subclasscheck__(cls) + return abc.ABCMeta.__subclasscheck__(self, cls) class FrozenSet(frozenset, AbstractSet[T_co], metaclass=_FrozenSetMeta, From cc999722865e7a6f7250f3c2b318f3fa530385db Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Sep 2016 22:47:06 +0200 Subject: [PATCH 09/27] Revert "Skip GenericMeta in FrozenSetMeta MRO" This reverts commit fa4a681d8624d75bd71816b3473d9e85a526513d. --- src/typing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/typing.py b/src/typing.py index a0d884a1..17168a5e 100644 --- a/src/typing.py +++ b/src/typing.py @@ -844,6 +844,7 @@ class GenericMeta(TypingMeta, abc.ABCMeta): def __new__(cls, name, bases, namespace, tvars=None, args=None, origin=None, extra=None): self = super().__new__(cls, name, bases, namespace, _root=True) + if tvars is not None: # Called from __getitem__() below. assert origin is not None @@ -1599,7 +1600,7 @@ class _FrozenSetMeta(GenericMeta): def __subclasscheck__(self, cls): if issubclass(cls, Set): return False - return abc.ABCMeta.__subclasscheck__(self, cls) + return super().__subclasscheck__(cls) class FrozenSet(frozenset, AbstractSet[T_co], metaclass=_FrozenSetMeta, From b587dc0fc622d5b51057a88d2aeee55851d5757b Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Sep 2016 23:44:45 +0200 Subject: [PATCH 10/27] Remove redundant metaclass for frozenset --- src/typing.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/typing.py b/src/typing.py index 17168a5e..6ef3145a 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1589,22 +1589,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): From e25b303f2a7c82d3870bdc6080ff31334d96ed1f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Sep 2016 23:51:04 +0200 Subject: [PATCH 11/27] Some formatting --- src/typing.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/typing.py b/src/typing.py index 6ef3145a..23349193 100644 --- a/src/typing.py +++ b/src/typing.py @@ -539,7 +539,9 @@ def __new__(cls, parameters=None, _root=False): if not isinstance(t1, type): continue if any(isinstance(t2, type) and issubclass(t1, t2) - for t2 in all_params - {t1} if not (isinstance(t2, GenericMeta) and t2.__origin__ is not None)): + 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: @@ -685,12 +687,14 @@ def __hash__(self): def __instancecheck__(self, obj): if self.__tuple_params__ == None: return isinstance(obj, tuple) - raise TypeError("Parameterized Tuple cannot be used with isinstance().") + raise TypeError("Parameterized Tuple cannot be used " + "with isinstance().") def __subclasscheck__(self, cls): if self.__tuple_params__ == None: return issubclass(cls, tuple) - raise TypeError("Parameterized Tuple cannot be used with issubclass().") + raise TypeError("Parameterized Tuple cannot be used " + "with issubclass().") Tuple = _Tuple(_root=True) @@ -777,13 +781,15 @@ def __instancecheck__(self, obj): if self.__args__ is None and self.__result__ is None: return isinstance(obj, collections_abc.Callable) else: - raise TypeError("Parameterized Callable cannot be used with isinstance().") + raise TypeError("Parameterized Callable cannot be used " + "with isinstance().") def __subclasscheck__(self, cls): if self.__args__ is None and self.__result__ is None: return issubclass(cls, collections_abc.Callable) else: - raise TypeError("Parameterized Callable cannot be used with issubclass().") + raise TypeError("Parameterized Callable cannot be used " + "with issubclass().") Callable = _Callable(_root=True) @@ -980,9 +986,12 @@ def __instancecheck__(self, instance): def __subclasscheck__(self, cls): if self is Generic: - raise TypeError("Class %r can't 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 can't be used with class or instance checks") + 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 not None: From 3ccb7a9038bc5b895ef378ba47f40b213d62f867 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 19 Sep 2016 00:24:23 +0200 Subject: [PATCH 12/27] Fix async/await tests --- src/test_typing.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index 5207c86f..ace88999 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -1114,13 +1114,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') @@ -1129,8 +1124,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') @@ -1138,8 +1131,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): From 0606059f8d2336068732cec5200b813edbc8fac1 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 19 Sep 2016 00:27:03 +0200 Subject: [PATCH 13/27] Fixed ContextManger test --- src/test_typing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test_typing.py b/src/test_typing.py index ace88999..b9ccb38e 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -1344,7 +1344,6 @@ def manager(): cm = manager() self.assertIsInstance(cm, typing.ContextManager) - self.assertIsInstance(cm, typing.ContextManager[int]) self.assertNotIsInstance(42, typing.ContextManager) From 8b909d6b53b331b7371e0b29a6c0fe4f6250215f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 19 Sep 2016 01:14:49 +0200 Subject: [PATCH 14/27] Be consistent about _TypeAlias --- src/test_typing.py | 3 --- src/typing.py | 23 ++++++++++++----------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index b9ccb38e..ddf4df79 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -1505,9 +1505,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]) diff --git a/src/typing.py b/src/typing.py index 23349193..fea5e1dd 100644 --- a/src/typing.py +++ b/src/typing.py @@ -195,12 +195,10 @@ def __repr__(self): class _TypeAlias(TypingBase, metaclass=TypingMeta, _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') @@ -247,18 +245,21 @@ def __getitem__(self, parameter): 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) and parameter != self.type_var: - 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 not isinstance(self.type_var, TypeVar): - raise TypeError("Type aliases cannot be used with issubclass().") + raise TypeError("Parameterized type aliases cannot be used " + "with issubclass().") return issubclass(cls, self.impl_type) From 554f2edce00608200db38422cc4441a8c852ef81 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 19 Sep 2016 01:21:14 +0200 Subject: [PATCH 15/27] Removed debugging print() --- src/typing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/typing.py b/src/typing.py index fea5e1dd..8c3bf75c 100644 --- a/src/typing.py +++ b/src/typing.py @@ -838,7 +838,6 @@ def tp_cache(func): wraps(func) def inner(*args, **kwargs): if any(not isinstance(arg, collections_abc.Hashable) for arg in args): - print("Not Hashable") return func(*args, **kwargs) else: return cached(*args, **kwargs) From 11189b05a7066084bb35a0c469b51ac25187a4bd Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 19 Sep 2016 21:09:30 +0200 Subject: [PATCH 16/27] Simplified code --- src/typing.py | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/typing.py b/src/typing.py index 8c3bf75c..2b57e482 100644 --- a/src/typing.py +++ b/src/typing.py @@ -131,11 +131,18 @@ def __repr__(self): return '%s.%s' % (self.__module__, _qualname(self)) -class TypingBase: +class TypingBase(metaclass=TypingMeta, _root=True): """Indicator of special typing constructs.""" + # things that are not classes also need these + def _eval_type(self, globalns, localns): + return self + + def _get_type_vars(self, tvars): + pass + -class Final(TypingBase): +class Final(TypingBase, _root=True): """Mix-in class to prevent instantiation.""" __slots__ = () @@ -146,7 +153,7 @@ def __new__(self, *args, _root=False, **kwds): raise TypeError("Cannot instantiate %r" % self) -class _ForwardRef(TypingBase): +class _ForwardRef(TypingBase, _root=True): """Wrapper to hold a forward reference.""" def __init__(self, arg): @@ -192,7 +199,7 @@ def __repr__(self): return '_ForwardRef(%r)' % (self.__forward_arg__,) -class _TypeAlias(TypingBase, metaclass=TypingMeta, _root=True): +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 cannot @@ -320,7 +327,7 @@ def _type_repr(obj): return repr(obj) -class _Any(Final, metaclass=TypingMeta, _root=True): +class _Any(Final, _root=True): """Special type indicating an unconstrained type. - Any object is an instance of Any. @@ -334,12 +341,6 @@ def __instancecheck__(self, obj): def __subclasscheck__(self, cls): raise TypeError("Any cannot be used with issubclass().") - def _eval_type(self, globalns, localns): - return self - - def _get_type_vars(self, tvars): - pass - def __repr__(self): cls = type(self) return '{}.{}'.format(cls.__module__, cls.__name__[1:]) @@ -348,7 +349,7 @@ def __repr__(self): Any = _Any(_root=True) -class TypeVar(TypingBase, metaclass=TypingMeta, _root=True): +class TypeVar(TypingBase, _root=True): """Type variable. Usage:: @@ -415,9 +416,6 @@ def _get_type_vars(self, tvars): if self not in tvars: tvars.append(self) - def _eval_type(self, globalns, localns): - return self - def __repr__(self): if self.__covariant__: prefix = '+' @@ -449,7 +447,7 @@ def __subclasscheck__(self, cls): AnyStr = TypeVar('AnyStr', bytes, str) -class _Union(Final, metaclass=TypingMeta, _root=True): +class _Union(Final, _root=True): """Union type; Union[X, Y] means either X or Y. To define a union, use e.g. Union[int, str]. Details: @@ -600,7 +598,7 @@ def __subclasscheck__(self, cls): Union = _Union(_root=True) -class _Optional(Final, metaclass=TypingMeta, _root=True): +class _Optional(Final, _root=True): """Optional type. Optional[X] is equivalent to Union[X, type(None)]. @@ -618,7 +616,7 @@ def __getitem__(self, arg): Optional = _Optional(_root=True) -class _Tuple(Final, metaclass=TypingMeta, _root=True): +class _Tuple(Final, _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 @@ -701,7 +699,7 @@ def __subclasscheck__(self, cls): Tuple = _Tuple(_root=True) -class _Callable(Final, metaclass=TypingMeta, _root=True): +class _Callable(Final, _root=True): """Callable type; Callable[[int], str] is a function of (int) -> str. The subscription syntax must always be used with exactly two @@ -1036,7 +1034,7 @@ def __new__(cls, *args, **kwds): return obj -class _ClassVar(Final, metaclass=TypingMeta, _root=True): +class _ClassVar(Final, _root=True): """Special type construct to mark class variables. An annotation wrapped in ClassVar indicates that a given From 4c91c3b86781cda840bb64a411e5b52c0db605a5 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 23 Sep 2016 00:14:17 +0200 Subject: [PATCH 17/27] Increased cache size to 50; start backporting changes to Python 2 --- python2/test_typing.py | 112 ++++-------------------------------- python2/typing.py | 125 ++++++++++++++++++++--------------------- src/test_typing.py | 4 +- src/typing.py | 2 +- 4 files changed, 75 insertions(+), 168 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index 3840fe5e..45a9ab7e 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -61,18 +61,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') @@ -97,22 +90,6 @@ def test_cannot_subscript(self): 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 +100,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 +110,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 +152,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 +166,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) @@ -354,32 +303,6 @@ 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): @@ -1274,22 +1197,14 @@ 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]) 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]) + # 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 +1217,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]) diff --git a/python2/typing.py b/python2/typing.py index 479da8c3..e7ff0e48 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -123,6 +123,18 @@ def __repr__(self): return '%s.%s' % (self.__module__, _qualname(self)) +class TypingBase(object): + """Indicator of special typing constructs.""" + __metaclass__ = TypingMeta + + # things that are not classes also need these + def _eval_type(self, globalns, localns): + return self + + def _get_type_vars(self, tvars): + pass + + class Final(object): """Mix-in class to prevent instantiation.""" @@ -132,10 +144,10 @@ def __new__(self, *args, **kwds): raise TypeError("Cannot instantiate %r" % self.__class__) -class _ForwardRef(TypingMeta): +class _ForwardRef(TypingBase): """Wrapper to hold a forward reference.""" - def __new__(cls, arg): + def __init__(self, arg): if not isinstance(arg, basestring): raise TypeError('ForwardRef must be a string -- got %r' % (arg,)) try: @@ -143,7 +155,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 +165,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,28 +184,19 @@ 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') @@ -225,11 +226,10 @@ 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) self.name = name - self.type_var = type_var + self.type_var = _type_check(type_var, "Type alias accepts only types") self.impl_type = impl_type self.type_checker = type_checker @@ -237,36 +237,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 +274,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 +296,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 +327,7 @@ def __new__(cls, name, bases, namespace): return self -class _ClassVar(object): +class _ClassVar(TypingBase): """Special type construct to mark class variables. An annotation wrapped in ClassVar indicates that a given @@ -354,7 +351,7 @@ def __init__(self, tp=None, _root=False): if _root: self.__type__ = tp else: - raise TypeError('Cannot initialize {}'.format(cls.__name__[1:])) + raise TypeError('Cannot instantiate {}'.format(cls.__name__[1:])) def __getitem__(self, item): cls = type(self) @@ -401,26 +398,34 @@ 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(TypingBase): """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 __init__(self, _root=False): + if not _root: + raise TypeError('Cannot instantiate {}'.format(cls.__name__[1:])) + + def __repr__(self): + cls = type(self) + return '{}.{}'.format(cls.__module__, cls.__name__[1:]) + + 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 +433,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:: @@ -475,11 +480,11 @@ def longest(x: A, y: A) -> A: __metaclass__ = TypeVarMeta - def __new__(cls, name, *constraints, **kwargs): + def __init__(self, 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 +499,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 +517,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. diff --git a/src/test_typing.py b/src/test_typing.py index ddf4df79..32096b7a 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -64,9 +64,9 @@ def test_any_instance_type_error(self): def test_any_subclass_type_error(self): with self.assertRaises(TypeError): - self.assertTrue(issubclass(Employee, Any)) + issubclass(Employee, Any) with self.assertRaises(TypeError): - self.assertFalse(issubclass(Any, Employee)) + issubclass(Any, Employee) def test_repr(self): self.assertEqual(repr(Any), 'typing.Any') diff --git a/src/typing.py b/src/typing.py index 2b57e482..acc17a5e 100644 --- a/src/typing.py +++ b/src/typing.py @@ -832,7 +832,7 @@ def _next_in_mro(cls): def tp_cache(func): - cached = lru_cache(maxsize=25)(func) + cached = lru_cache(maxsize=50)(func) wraps(func) def inner(*args, **kwargs): if any(not isinstance(arg, collections_abc.Hashable) for arg in args): From 192000937d5c028a48316e6b018ecad348110794 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 23 Sep 2016 09:07:01 +0200 Subject: [PATCH 18/27] Backport Union and Optional to Python 2 --- python2/test_typing.py | 35 +++----- python2/typing.py | 188 ++++++++++++++++++++--------------------- src/typing.py | 1 - 3 files changed, 100 insertions(+), 124 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index 45a9ab7e..9e93728a 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -178,8 +178,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] @@ -208,18 +216,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) @@ -232,13 +228,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] @@ -275,10 +264,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]) diff --git a/python2/typing.py b/python2/typing.py index e7ff0e48..8c61ff55 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -410,6 +410,7 @@ class _Any(TypingBase): __slots__ = () def __init__(self, _root=False): + cls = type(self) if not _root: raise TypeError('Cannot instantiate {}'.format(cls.__name__[1:])) @@ -536,19 +537,80 @@ 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(TypingBase): + """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 + + def __new__(cls, parameters=None, _root=False): + if not _root: + raise TypeError('Cannot instantiate {}'.format(cls.__name__[1:])) + self = super(_Union, cls).__new__(cls) 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)) @@ -572,22 +634,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 @@ -598,15 +654,15 @@ 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__() + cls = type(self) + r = '{}.{}'.format(cls.__module__, cls.__name__[1:]) if self.__union_params__: r += '[%s]' % (', '.join(_type_repr(t) for t in self.__union_params__)) @@ -620,11 +676,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__ @@ -635,81 +690,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__) - - -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 + raise TypeError("Unions cannot be used with issubclass().") - - 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): @@ -719,12 +703,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(TypingBase): """Optional type. Optional[X] is equivalent to Union[X, type(None)]. @@ -733,6 +713,18 @@ class Optional(Final): __metaclass__ = OptionalMeta __slots__ = () + def __init__(self, _root=False): + cls = type(self) + if not _root: + raise TypeError('Cannot instantiate {}'.format(cls.__name__[1:])) + + 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.""" diff --git a/src/typing.py b/src/typing.py index acc17a5e..3b3db459 100644 --- a/src/typing.py +++ b/src/typing.py @@ -545,7 +545,6 @@ def __new__(cls, parameters=None, _root=False): # 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.__union_params__ = tuple(t for t in params if t in all_params) self.__union_set_params__ = frozenset(self.__union_params__) return self From f003eca45e059babf543d5ec24b135961fafad20 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 23 Sep 2016 23:43:33 +0200 Subject: [PATCH 19/27] Finished backport to Python 2; some polishing is needed --- python2/test_typing.py | 106 ++++-------------- python2/typing.py | 246 +++++++++++++++++------------------------ src/typing.py | 32 ++---- 3 files changed, 132 insertions(+), 252 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index 9e93728a..9fb61f09 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 from typing import Callable from typing import Generic, ClassVar from typing import cast @@ -291,14 +291,16 @@ def Elem(*args): 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]) @@ -314,21 +316,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') @@ -346,17 +334,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]) @@ -499,6 +479,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') @@ -748,52 +734,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): @@ -866,7 +806,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) @@ -875,7 +814,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): @@ -1032,10 +970,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): diff --git a/python2/typing.py b/python2/typing.py index 8c61ff55..60eb5039 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -134,14 +134,20 @@ def _eval_type(self, globalns, localns): def _get_type_vars(self, tvars): pass + def __repr__(self): + cls = type(self) + return '{}.{}'.format(cls.__module__, cls.__name__[1:]) + -class Final(object): +class Final(TypingBase): """Mix-in class to prevent instantiation.""" __slots__ = () - def __new__(self, *args, **kwds): - raise TypeError("Cannot instantiate %r" % self.__class__) + def __init__(self, _root=False): + cls = type(self) + if _root is not True: + raise TypeError('Cannot instantiate {}'.format(cls.__name__[1:])) class _ForwardRef(TypingBase): @@ -327,7 +333,7 @@ def __new__(cls, name, bases, namespace): return self -class _ClassVar(TypingBase): +class _ClassVar(Final): """Special type construct to mark class variables. An annotation wrapped in ClassVar indicates that a given @@ -347,11 +353,8 @@ class Starship: __metaclass__ = ClassVarMeta def __init__(self, tp=None, _root=False): - cls = type(self) - if _root: - self.__type__ = tp - else: - raise TypeError('Cannot instantiate {}'.format(cls.__name__[1:])) + super(_ClassVar, self).__init__(_root) + self.__type__ = tp def __getitem__(self, item): cls = type(self) @@ -371,11 +374,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__)) @@ -399,7 +401,7 @@ def __new__(cls, name, bases, namespace): return self -class _Any(TypingBase): +class _Any(Final): """Special type indicating an unconstrained type. - Any object is an instance of Any. @@ -409,15 +411,6 @@ class _Any(TypingBase): __metaclass__ = AnyMeta __slots__ = () - def __init__(self, _root=False): - cls = type(self) - if not _root: - raise TypeError('Cannot instantiate {}'.format(cls.__name__[1:])) - - def __repr__(self): - cls = type(self) - return '{}.{}'.format(cls.__module__, cls.__name__[1:]) - def __instancecheck__(self, obj): raise TypeError("Any cannot be used with isinstance().") @@ -537,12 +530,14 @@ def __subclasscheck__(self, cls): class UnionMeta(TypingMeta): + """Metaclass for Union.""" + def __new__(cls, name, bases, namespace): cls.assert_no_subclassing(bases) return super(UnionMeta, cls).__new__(cls, name, bases, namespace) -class _Union(TypingBase): +class _Union(Final): """Union type; Union[X, Y] means either X or Y. To define a union, use e.g. Union[int, str]. Details: @@ -596,9 +591,10 @@ class Manager(Employee): pass __metaclass__ = UnionMeta + def __init__(self, parameters=None, _root=False): + super(_Union, self).__init__(_root) + def __new__(cls, parameters=None, _root=False): - if not _root: - raise TypeError('Cannot instantiate {}'.format(cls.__name__[1:])) self = super(_Union, cls).__new__(cls) if parameters is None: self.__union_params__ = None @@ -661,8 +657,7 @@ def _get_type_vars(self, tvars): _get_type_vars(self.__union_params__, tvars) def __repr__(self): - cls = type(self) - r = '{}.{}'.format(cls.__module__, cls.__name__[1:]) + r = super(_Union, self).__repr__() if self.__union_params__: r += '[%s]' % (', '.join(_type_repr(t) for t in self.__union_params__)) @@ -704,7 +699,7 @@ def __new__(cls, name, bases, namespace): return super(OptionalMeta, cls).__new__(cls, name, bases, namespace) -class _Optional(TypingBase): +class _Optional(Final): """Optional type. Optional[X] is equivalent to Union[X, type(None)]. @@ -713,11 +708,6 @@ class _Optional(TypingBase): __metaclass__ = OptionalMeta __slots__ = () - def __init__(self, _root=False): - cls = type(self) - if not _root: - raise TypeError('Cannot instantiate {}'.format(cls.__name__[1:])) - def __getitem__(self, arg): arg = _type_check(arg, "Optional[t] requires a single type.") return Union[arg, type(None)] @@ -729,13 +719,28 @@ def __getitem__(self, arg): 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(Final): + """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 Sequence[T]. + """ + + __metaclass__ = TupleMeta + + def __init__(self, parameters=None, + use_ellipsis=False, _root=False): + super(_Tuple, self).__init__(_root) self.__tuple_params__ = parameters self.__tuple_use_ellipsis__ = use_ellipsis - return self def _get_type_vars(self, tvars): if self.__tuple_params__: @@ -749,11 +754,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__: @@ -777,12 +781,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__) @@ -791,51 +793,46 @@ 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__))) - + if self.__tuple_params__ == None: + return issubclass(cls, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with issubclass().") -class Tuple(Final): - """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 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(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. + + There is no syntax to indicate optional or keyword arguments, + such function types are rarely used as callback types. + """ + + __metaclass__ = CallableMeta + + def __init__(self, args=None, result=None, _root=False): + super(_Callable, self).__init__(_root) + + def __new__(cls, args=None, result=None, _root=False): if args is None and result is None: pass # Must be 'class Callable'. else: @@ -848,7 +845,7 @@ 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 = super(_Callable, cls).__new__(cls) self.__args__ = args self.__result__ = result return self @@ -868,11 +865,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 = '...' @@ -889,12 +885,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__) @@ -909,32 +903,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): @@ -1114,44 +1094,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. diff --git a/src/typing.py b/src/typing.py index 3b3db459..5cb0bcad 100644 --- a/src/typing.py +++ b/src/typing.py @@ -141,6 +141,10 @@ def _eval_type(self, globalns, localns): def _get_type_vars(self, tvars): pass + def __repr__(self): + cls = type(self) + return '{}.{}'.format(cls.__module__, cls.__name__[1:]) + class Final(TypingBase, _root=True): """Mix-in class to prevent instantiation.""" @@ -150,7 +154,7 @@ class Final(TypingBase, _root=True): def __new__(self, *args, _root=False, **kwds): if _root: return object.__new__(self) - raise TypeError("Cannot instantiate %r" % self) + raise TypeError("Cannot instantiate or subclass %r" % self) class _ForwardRef(TypingBase, _root=True): @@ -341,10 +345,6 @@ def __instancecheck__(self, obj): def __subclasscheck__(self, cls): raise TypeError("Any cannot be used with issubclass().") - def __repr__(self): - cls = type(self) - return '{}.{}'.format(cls.__module__, cls.__name__[1:]) - Any = _Any(_root=True) @@ -562,8 +562,7 @@ def _get_type_vars(self, tvars): _get_type_vars(self.__union_params__, tvars) def __repr__(self): - cls = type(self) - r = '{}.{}'.format(cls.__module__, cls.__name__[1:]) + r = super().__repr__() if self.__union_params__: r += '[%s]' % (', '.join(_type_repr(t) for t in self.__union_params__)) @@ -603,10 +602,6 @@ class _Optional(Final, _root=True): Optional[X] is equivalent to Union[X, type(None)]. """ - def __repr__(self): - cls = type(self) - return '{}.{}'.format(cls.__module__, cls.__name__[1:]) - def __getitem__(self, arg): arg = _type_check(arg, "Optional[t] requires a single type.") return Union[arg, type(None)] @@ -645,8 +640,7 @@ def _eval_type(self, globalns, localns): return self.__class__(p, _root=True) def __repr__(self): - cls = type(self) - r = '{}.{}'.format(cls.__module__, cls.__name__[1:]) + r = super().__repr__() if self.__tuple_params__ is not None: params = [_type_repr(p) for p in self.__tuple_params__] if self.__tuple_use_ellipsis__: @@ -743,8 +737,7 @@ def _eval_type(self, globalns, localns): return self.__class__(args, result, _root=True) def __repr__(self): - cls = type(self) - r = '{}.{}'.format(cls.__module__, cls.__name__[1:]) + r = super().__repr__() if self.__args__ is not None or self.__result__ is not None: if self.__args__ is Ellipsis: args_r = '...' @@ -1071,11 +1064,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().__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__)) From e130c770d7fd3bb3a908f259e7587eb8a600700e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 24 Sep 2016 16:44:32 +0200 Subject: [PATCH 20/27] Added many unit tests for bugs fixed by PR --- python2/test_typing.py | 70 +++++++++++++++++++++++++++++++++++++++- src/test_typing.py | 72 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 140 insertions(+), 2 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index 9fb61f09..98172c59 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, List +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): @@ -80,10 +84,15 @@ 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): @@ -244,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 @@ -254,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] @@ -353,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]): @@ -361,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(): @@ -540,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') @@ -722,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): @@ -1116,10 +1182,12 @@ def test_basics(self): pat = re.compile('[a-z]+', re.I) self.assertIsSubclass(pat.__class__, Pattern) self.assertIsSubclass(type(pat), Pattern) + self.assertIsInstance(pat, Pattern) mat = pat.search('12345abcde.....') self.assertIsSubclass(mat.__class__, Match) self.assertIsSubclass(type(mat), Match) + self.assertIsInstance(mat, Match) # these should just work p = Pattern[Union[str, bytes]] diff --git a/src/test_typing.py b/src/test_typing.py index 32096b7a..172d40ad 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): @@ -81,10 +85,15 @@ 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): @@ -245,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 @@ -252,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] @@ -354,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]): @@ -362,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(): @@ -565,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') @@ -747,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): @@ -1485,10 +1553,12 @@ def test_basics(self): pat = re.compile('[a-z]+', re.I) self.assertIsSubclass(pat.__class__, Pattern) self.assertIsSubclass(type(pat), Pattern) + self.assertIsInstance(pat, Pattern) mat = pat.search('12345abcde.....') self.assertIsSubclass(mat.__class__, Match) self.assertIsSubclass(type(mat), Match) + self.assertIsInstance(mat, Match) # these should just work p = Pattern[Union[str, bytes]] From fe2d3b797ea7adf809638ff953baa9618993e19a Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 24 Sep 2016 17:01:01 +0200 Subject: [PATCH 21/27] Updated docstrings and other minor things --- python2/typing.py | 9 ++++----- src/typing.py | 19 +++++++++++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/python2/typing.py b/python2/typing.py index 60eb5039..e62a43b8 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -145,9 +145,8 @@ class Final(TypingBase): __slots__ = () def __init__(self, _root=False): - cls = type(self) if _root is not True: - raise TypeError('Cannot instantiate {}'.format(cls.__name__[1:])) + raise TypeError('Cannot instantiate or subclass %r' % self) class _ForwardRef(TypingBase): @@ -353,8 +352,8 @@ class Starship: __metaclass__ = ClassVarMeta def __init__(self, tp=None, _root=False): - super(_ClassVar, self).__init__(_root) self.__type__ = tp + super(_ClassVar, self).__init__(_root) def __getitem__(self, item): cls = type(self) @@ -731,16 +730,16 @@ class _Tuple(Final): 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]. + To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. """ __metaclass__ = TupleMeta def __init__(self, parameters=None, use_ellipsis=False, _root=False): - super(_Tuple, self).__init__(_root) self.__tuple_params__ = parameters self.__tuple_use_ellipsis__ = use_ellipsis + super(_Tuple, self).__init__(_root) def _get_type_vars(self, tvars): if self.__tuple_params__: diff --git a/src/typing.py b/src/typing.py index 5cb0bcad..89cbf24a 100644 --- a/src/typing.py +++ b/src/typing.py @@ -617,7 +617,7 @@ class _Tuple(Final, _root=True): 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]. + To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. """ def __init__(self, parameters=None, @@ -1587,7 +1587,22 @@ def __new__(cls, *args, **kwds): return set.__new__(cls, *args, **kwds) -class FrozenSet(frozenset, AbstractSet[T_co], extra=frozenset): +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): __slots__ = () def __new__(cls, *args, **kwds): From e4cebff3dfb7c2cd8c70396ca24a033a464183a5 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 26 Sep 2016 01:47:59 +0200 Subject: [PATCH 22/27] Optimized caching + first part of response to comments --- src/test_typing.py | 2 +- src/typing.py | 99 ++++++++++++++++++++++++++++++---------------- 2 files changed, 65 insertions(+), 36 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index 172d40ad..6543005f 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -1601,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 89cbf24a..f1a8f26d 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1,6 +1,5 @@ import abc from abc import abstractmethod, abstractproperty -from functools import lru_cache, wraps import collections import contextlib import functools @@ -90,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. @@ -128,12 +134,26 @@ 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.""" + 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 @@ -143,7 +163,11 @@ def _get_type_vars(self, tvars): def __repr__(self): cls = type(self) - return '{}.{}'.format(cls.__module__, cls.__name__[1:]) + 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(TypingBase, _root=True): @@ -151,10 +175,11 @@ class Final(TypingBase, _root=True): __slots__ = () - def __new__(self, *args, _root=False, **kwds): - if _root: - return object.__new__(self) - raise TypeError("Cannot instantiate or subclass %r" % self) + 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(TypingBase, _root=True): @@ -193,6 +218,14 @@ 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__ + + def __hash__(self): + return hash(self.__forward_arg__) + def __instancecheck__(self, obj): raise TypeError("Forward references cannot be used with isinstance().") @@ -214,19 +247,6 @@ class _TypeAlias(TypingBase, _root=True): __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. @@ -261,6 +281,14 @@ def __getitem__(self, parameter): 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): if not isinstance(self.type_var, TypeVar): raise TypeError("Parameterized type aliases cannot be used " @@ -447,6 +475,17 @@ def __subclasscheck__(self, cls): AnyStr = TypeVar('AnyStr', bytes, str) +def _tp_cache(func): + cached = functools.lru_cache()(func) + @functools.wraps(func) + def inner(*args, **kwargs): + if any(not isinstance(arg, collections_abc.Hashable) for arg in args): + return func(*args, **kwargs) + else: + return cached(*args, **kwargs) + return inner + + class _Union(Final, _root=True): """Union type; Union[X, Y] means either X or Y. @@ -499,8 +538,8 @@ class Manager(Employee): pass - You can use Optional[X] as a shorthand for Union[X, None]. """ - def __new__(cls, parameters=None, _root=False): - self = super().__new__(cls, _root=_root) + def __new__(cls, parameters=None, *args, _root=False): + self = super().__new__(cls, parameters, *args, _root=_root) if parameters is None: self.__union_params__ = None self.__union_set_params__ = None @@ -568,6 +607,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( @@ -674,7 +714,7 @@ def __eq__(self, other): 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): if self.__tuple_params__ == None: @@ -823,17 +863,6 @@ def _next_in_mro(cls): return next_in_mro -def tp_cache(func): - cached = lru_cache(maxsize=50)(func) - wraps(func) - def inner(*args, **kwargs): - if any(not isinstance(arg, collections_abc.Hashable) for arg in args): - return func(*args, **kwargs) - else: - return cached(*args, **kwargs) - return inner - - class GenericMeta(TypingMeta, abc.ABCMeta): """Metaclass for generic types.""" @@ -919,7 +948,7 @@ def __eq__(self, other): def __hash__(self): return hash((self.__name__, self.__parameters__)) - @tp_cache + @_tp_cache def __getitem__(self, params): if not isinstance(params, tuple): params = (params,) From 9b443cb45934b8e515c62ab5ad571b5571008139 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 26 Sep 2016 02:31:37 +0200 Subject: [PATCH 23/27] Second part of response to comments. Need to port all comments etc. to Python 2 --- src/typing.py | 114 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 78 insertions(+), 36 deletions(-) diff --git a/src/typing.py b/src/typing.py index f1a8f26d..24677a7f 100644 --- a/src/typing.py +++ b/src/typing.py @@ -138,9 +138,15 @@ def __repr__(self): return '%s.%s' % (self.__module__, qname) -class TypingBase(metaclass=TypingMeta, _root=True): +class _TypingBase(metaclass=TypingMeta, _root=True): """Indicator of special typing constructs.""" + __slots__ = () + + + def __init__(self, *args, **kwds): + pass + def __new__(cls, *args, **kwds): """Constructor. @@ -154,7 +160,7 @@ def __new__(cls, *args, **kwds): raise TypeError("Cannot subclass %r" % cls) return object.__new__(cls) - # things that are not classes also need these + # Things that are not classes also need these. def _eval_type(self, globalns, localns): return self @@ -170,7 +176,7 @@ def __call__(self, *args, **kwds): raise TypeError("Cannot instantiate %r" % type(self)) -class Final(TypingBase, _root=True): +class _FinalTypingBase(_TypingBase, _root=True): """Mix-in class to prevent instantiation.""" __slots__ = () @@ -182,10 +188,15 @@ def __new__(cls, *args, _root=False, **kwds): raise TypeError("Cannot instantiate %r" % cls) -class _ForwardRef(TypingBase, _root=True): +class _ForwardRef(_TypingBase, _root=True): """Wrapper to hold a forward reference.""" + __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: @@ -221,10 +232,11 @@ def _eval_type(self, globalns, localns): def __eq__(self, other): if not isinstance(other, _ForwardRef): return NotImplemented - return self.__forward_arg__ == other.__forward_arg__ + return (self.__forward_arg__ == other.__forward_arg__ and + self.__forward_frame__ == other.__forward_frame__) def __hash__(self): - return hash(self.__forward_arg__) + return hash((self.__forward_arg__, self.__forward_frame__)) def __instancecheck__(self, obj): raise TypeError("Forward references cannot be used with isinstance().") @@ -236,7 +248,7 @@ def __repr__(self): return '_ForwardRef(%r)' % (self.__forward_arg__,) -class _TypeAlias(TypingBase, _root=True): +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 cannot @@ -261,8 +273,9 @@ def __init__(self, name, type_var, impl_type, type_checker): assert isinstance(name, str), repr(name) 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_check(type_var, "Type alias accepts only types") + self.type_var = type_var self.impl_type = impl_type self.type_checker = type_checker @@ -304,7 +317,7 @@ def __subclasscheck__(self, cls): def _get_type_vars(types, tvars): for t in types: - if isinstance(t, TypingMeta) or isinstance(t, TypingBase): + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): t._get_type_vars(tvars) @@ -315,7 +328,7 @@ def _type_vars(types): def _eval_type(t, globalns, localns): - if isinstance(t, TypingMeta) or isinstance(t, TypingBase): + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): return t._eval_type(globalns, localns) else: return t @@ -337,7 +350,7 @@ def _type_check(arg, msg): return type(None) if isinstance(arg, str): arg = _ForwardRef(arg) - if not isinstance(arg, (type, TypingBase)) and not callable(arg): + if not isinstance(arg, (type, _TypingBase)) and not callable(arg): raise TypeError(msg + " Got %.100r." % (arg,)) return arg @@ -359,7 +372,7 @@ def _type_repr(obj): return repr(obj) -class _Any(Final, _root=True): +class _Any(_FinalTypingBase, _root=True): """Special type indicating an unconstrained type. - Any object is an instance of Any. @@ -367,6 +380,8 @@ class _Any(Final, _root=True): - As a special case, Any and object are subclasses of each other. """ + __slots__ = () + def __instancecheck__(self, obj): raise TypeError("Any cannot be used with isinstance().") @@ -377,7 +392,7 @@ def __subclasscheck__(self, cls): Any = _Any(_root=True) -class TypeVar(TypingBase, _root=True): +class TypeVar(_TypingBase, _root=True): """Type variable. Usage:: @@ -422,8 +437,13 @@ def longest(x: A, y: A) -> A: A.__constraints__ == (str, bytes) """ + __slots__ = ('__name__', '__bound__', '__constraints__', + '__covariant__', '__contravariant__') + def __init__(self, name, *constraints, bound=None, covariant=False, contravariant=False): + super().__init__(name, *constraints, bound=bound, + covariant=covariant, contravariant=contravariant) self.__name__ = name if covariant and contravariant: raise ValueError("Bivariant types are not supported.") @@ -486,7 +506,7 @@ def inner(*args, **kwargs): return inner -class _Union(Final, _root=True): +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: @@ -538,6 +558,8 @@ class Manager(Employee): pass - 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: @@ -636,12 +658,14 @@ def __subclasscheck__(self, cls): Union = _Union(_root=True) -class _Optional(Final, _root=True): +class _Optional(_FinalTypingBase, _root=True): """Optional type. Optional[X] is equivalent to Union[X, type(None)]. """ + __slots__ = () + def __getitem__(self, arg): arg = _type_check(arg, "Optional[t] requires a single type.") return Union[arg, type(None)] @@ -650,7 +674,7 @@ def __getitem__(self, arg): Optional = _Optional(_root=True) -class _Tuple(Final, _root=True): +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 @@ -660,6 +684,8 @@ class _Tuple(Final, _root=True): To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. """ + __slots__ = ('__tuple_params__', '__tuple_use_ellipsis__') + def __init__(self, parameters=None, use_ellipsis=False, _root=False): self.__tuple_params__ = parameters @@ -732,7 +758,7 @@ def __subclasscheck__(self, cls): Tuple = _Tuple(_root=True) -class _Callable(Final, _root=True): +class _Callable(_FinalTypingBase, _root=True): """Callable type; Callable[[int], str] is a function of (int) -> str. The subscription syntax must always be used with exactly two @@ -743,6 +769,8 @@ class _Callable(Final, _root=True): 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 @@ -1055,7 +1083,7 @@ def __new__(cls, *args, **kwds): return obj -class _ClassVar(Final, _root=True): +class _ClassVar(_FinalTypingBase, _root=True): """Special type construct to mark class variables. An annotation wrapped in ClassVar indicates that a given @@ -1072,6 +1100,8 @@ class Starship: be used with isinstance() or issubclass(). """ + __slots__ = ('__type__',) + def __init__(self, tp=None, **kwds): self.__type__ = tp @@ -1079,14 +1109,16 @@ 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__: @@ -1547,52 +1579,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''))) @@ -1600,6 +1632,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; " @@ -1609,6 +1643,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; " @@ -1642,23 +1678,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'): @@ -1669,6 +1705,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; " @@ -1678,6 +1716,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; " @@ -1733,6 +1773,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]) From bd160a143ef676cce8073330b2af8053c2f3ee94 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 26 Sep 2016 09:30:26 +0200 Subject: [PATCH 24/27] Start backporting comments to Python2; improve caching --- python2/typing.py | 28 ++++++++++++++-------------- src/typing.py | 12 +++++++----- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/python2/typing.py b/python2/typing.py index e62a43b8..87a1084a 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -123,7 +123,7 @@ def __repr__(self): return '%s.%s' % (self.__module__, _qualname(self)) -class TypingBase(object): +class _TypingBase(object): """Indicator of special typing constructs.""" __metaclass__ = TypingMeta @@ -139,7 +139,7 @@ def __repr__(self): return '{}.{}'.format(cls.__module__, cls.__name__[1:]) -class Final(TypingBase): +class _FinalTypingBase(_TypingBase): """Mix-in class to prevent instantiation.""" __slots__ = () @@ -149,7 +149,7 @@ def __init__(self, _root=False): raise TypeError('Cannot instantiate or subclass %r' % self) -class _ForwardRef(TypingBase): +class _ForwardRef(_TypingBase): """Wrapper to hold a forward reference.""" def __init__(self, arg): @@ -195,7 +195,7 @@ def __repr__(self): return '_ForwardRef(%r)' % (self.__forward_arg__,) -class _TypeAlias(TypingBase): +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 cannot @@ -268,7 +268,7 @@ def __subclasscheck__(self, cls): def _get_type_vars(types, tvars): for t in types: - if isinstance(t, TypingMeta) or isinstance(t, TypingBase): + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): t._get_type_vars(tvars) @@ -279,7 +279,7 @@ def _type_vars(types): def _eval_type(t, globalns, localns): - if isinstance(t, TypingMeta) or isinstance(t, TypingBase): + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): return t._eval_type(globalns, localns) else: return t @@ -301,7 +301,7 @@ def _type_check(arg, msg): return type(None) if isinstance(arg, basestring): arg = _ForwardRef(arg) - if not isinstance(arg, (type, TypingBase)) and not callable(arg): + if not isinstance(arg, (type, _TypingBase)) and not callable(arg): raise TypeError(msg + " Got %.100r." % (arg,)) return arg @@ -332,7 +332,7 @@ def __new__(cls, name, bases, namespace): return self -class _ClassVar(Final): +class _ClassVar(_FinalTypingBase): """Special type construct to mark class variables. An annotation wrapped in ClassVar indicates that a given @@ -400,7 +400,7 @@ def __new__(cls, name, bases, namespace): return self -class _Any(Final): +class _Any(_FinalTypingBase): """Special type indicating an unconstrained type. - Any object is an instance of Any. @@ -426,7 +426,7 @@ def __new__(cls, name, bases, namespace): return super(TypeVarMeta, cls).__new__(cls, name, bases, namespace) -class TypeVar(TypingBase): +class TypeVar(_TypingBase): """Type variable. Usage:: @@ -536,7 +536,7 @@ def __new__(cls, name, bases, namespace): return super(UnionMeta, cls).__new__(cls, name, bases, namespace) -class _Union(Final): +class _Union(_FinalTypingBase): """Union type; Union[X, Y] means either X or Y. To define a union, use e.g. Union[int, str]. Details: @@ -698,7 +698,7 @@ def __new__(cls, name, bases, namespace): return super(OptionalMeta, cls).__new__(cls, name, bases, namespace) -class _Optional(Final): +class _Optional(_FinalTypingBase): """Optional type. Optional[X] is equivalent to Union[X, type(None)]. @@ -723,7 +723,7 @@ def __new__(cls, name, bases, namespace): return super(TupleMeta, cls).__new__(cls, name, bases, namespace) -class _Tuple(Final): +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 @@ -815,7 +815,7 @@ def __new__(cls, name, bases, namespace): return super(CallableMeta, cls).__new__(cls, name, bases, namespace) -class _Callable(Final): +class _Callable(_FinalTypingBase): """Callable type; Callable[[int], str] is a function of (int) -> str. The subscription syntax must always be used with exactly two diff --git a/src/typing.py b/src/typing.py index 24677a7f..0331e9df 100644 --- a/src/typing.py +++ b/src/typing.py @@ -498,11 +498,11 @@ def __subclasscheck__(self, cls): def _tp_cache(func): cached = functools.lru_cache()(func) @functools.wraps(func) - def inner(*args, **kwargs): - if any(not isinstance(arg, collections_abc.Hashable) for arg in args): - return func(*args, **kwargs) - else: - return cached(*args, **kwargs) + def inner(*args, **kwds): + try: + return cached(*args, **kwds) + except TypeError: + return func(*args, **kwds) return inner @@ -666,6 +666,7 @@ class _Optional(_FinalTypingBase, _root=True): __slots__ = () + @_tp_cache def __getitem__(self, arg): arg = _type_check(arg, "Optional[t] requires a single type.") return Union[arg, type(None)] @@ -717,6 +718,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,)) From 80ce5134ef36451331380ce99964b360ec5c9ecb Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 26 Sep 2016 09:38:50 +0200 Subject: [PATCH 25/27] Get rid of frozensetmeta, it causes random failures --- src/typing.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/typing.py b/src/typing.py index 0331e9df..fa354536 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1654,22 +1654,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): From c4f11e88b252d2625c302c3d85bd86d066381447 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 26 Sep 2016 13:23:49 +0200 Subject: [PATCH 26/27] Backported everything to Python 2 --- python2/test_typing.py | 2 +- python2/typing.py | 114 ++++++++++++++++++++++++----------------- 2 files changed, 69 insertions(+), 47 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index 98172c59..25472955 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -1230,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 87a1084a..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,14 +126,32 @@ 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. - # things that are not classes also need these + 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 @@ -136,7 +160,11 @@ def _get_type_vars(self, tvars): def __repr__(self): cls = type(self) - return '{}.{}'.format(cls.__module__, cls.__name__[1:]) + 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): @@ -144,15 +172,22 @@ class _FinalTypingBase(_TypingBase): __slots__ = () - def __init__(self, _root=False): - if _root is not True: - raise TypeError('Cannot instantiate or subclass %r' % self) + 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(_TypingBase): """Wrapper to hold a forward reference.""" + __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: @@ -206,18 +241,6 @@ class _TypeAlias(_TypingBase): __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. @@ -233,8 +256,9 @@ def __init__(self, name, type_var, impl_type, type_checker): assert isinstance(name, basestring), repr(name) 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_check(type_var, "Type alias accepts only types") + self.type_var = type_var self.impl_type = impl_type self.type_checker = type_checker @@ -350,10 +374,10 @@ class Starship: """ __metaclass__ = ClassVarMeta + __slots__ = ('__type__',) def __init__(self, tp=None, _root=False): self.__type__ = tp - super(_ClassVar, self).__init__(_root) def __getitem__(self, item): cls = type(self) @@ -472,8 +496,11 @@ def longest(x: A, y: A) -> A: """ __metaclass__ = TypeVarMeta + __slots__ = ('__name__', '__bound__', '__constraints__', + '__covariant__', '__contravariant__') 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) @@ -589,12 +616,10 @@ class Manager(Employee): pass """ __metaclass__ = UnionMeta + __slots__ = ('__union_params__', '__union_set_params__') - def __init__(self, parameters=None, _root=False): - super(_Union, self).__init__(_root) - - def __new__(cls, parameters=None, _root=False): - self = super(_Union, cls).__new__(cls) + def __new__(cls, parameters=None, *args, **kwds): + self = super(_Union, cls).__new__(cls, parameters, *args, **kwds) if parameters is None: self.__union_params__ = None self.__union_set_params__ = None @@ -734,12 +759,12 @@ class _Tuple(_FinalTypingBase): """ __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 - super(_Tuple, self).__init__(_root) def _get_type_vars(self, tvars): if self.__tuple_params__: @@ -827,11 +852,9 @@ class _Callable(_FinalTypingBase): """ __metaclass__ = CallableMeta + __slots__ = ('__args__', '__result__') def __init__(self, args=None, result=None, _root=False): - super(_Callable, self).__init__(_root) - - def __new__(cls, args=None, result=None, _root=False): if args is None and result is None: pass # Must be 'class Callable'. else: @@ -844,10 +867,8 @@ def __new__(cls, args=None, result=None, _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(_Callable, cls).__new__(cls) self.__args__ = args self.__result__ = result - return self def _get_type_vars(self, tvars): if self.__args__ and self.__args__ is not Ellipsis: @@ -1405,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 @@ -1442,6 +1470,7 @@ class ByteString(Sequence[int]): class List(list, MutableSequence[T]): + __slots__ = () __extra__ = list def __new__(cls, *args, **kwds): @@ -1452,6 +1481,7 @@ def __new__(cls, *args, **kwds): class Set(set, MutableSet[T]): + __slots__ = () __extra__ = set def __new__(cls, *args, **kwds): @@ -1461,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 @@ -1488,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): @@ -1516,6 +1536,7 @@ def __new__(cls, *args, **kwds): class DefaultDict(collections.defaultdict, MutableMapping[KT, VT]): + __slots__ = () __extra__ = collections.defaultdict def __new__(cls, *args, **kwds): @@ -1573,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 From 230a500b7f0081f192dcbd989597fbe72627321a Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 26 Sep 2016 23:47:03 +0200 Subject: [PATCH 27/27] Do not duplicate real errors while caching --- src/typing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/typing.py b/src/typing.py index fa354536..925d9e42 100644 --- a/src/typing.py +++ b/src/typing.py @@ -502,7 +502,8 @@ def inner(*args, **kwds): try: return cached(*args, **kwds) except TypeError: - return func(*args, **kwds) + pass # Do not duplicate real errors. + return func(*args, **kwds) return inner