diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst new file mode 100644 index 00000000000..c94bea120d5 --- /dev/null +++ b/Doc/library/typing.rst @@ -0,0 +1,15 @@ +:mod:`typing` --- Support for type hints +======================================== + +.. module:: typing + :synopsis: Support for type hints (see PEP 484). + +**Source code:** :source:`Lib/typing.py` + +-------------- + +This module supports type hints as specified by :pep:`484`. The most +fundamental support consists of the type :class:`Any`, :class:`Union`, +:class:`Tuple`, :class:`Callable`, :class:`TypeVar`, and +:class:`Generic`. For full specification please see :pep:`484`. For +a simplified introduction to type hints see :pep:`483`. diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py new file mode 100644 index 00000000000..c37e1130a11 --- /dev/null +++ b/Lib/test/test_typing.py @@ -0,0 +1,1373 @@ +from collections import namedtuple +import re +import sys +from unittest import TestCase, main +try: + from unittest import mock +except ImportError: + import mock # 3rd party install, for PY3.2. + +from typing import Any +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 Callable +from typing import Generic +from typing import cast +from typing import get_type_hints +from typing import no_type_check, no_type_check_decorator +from typing import NamedTuple +from typing import IO, TextIO, BinaryIO +from typing import Pattern, Match +import typing + + +class Employee: + pass + + +class Manager(Employee): + pass + + +class Founder(Employee): + pass + + +class ManagingFounder(Manager, Founder): + pass + + +class AnyTests(TestCase): + + def test_any_instance(self): + self.assertIsInstance(Employee(), Any) + self.assertIsInstance(42, Any) + self.assertIsInstance(None, Any) + self.assertIsInstance(object(), 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_repr(self): + self.assertEqual(repr(Any), 'typing.Any') + + def test_errors(self): + with self.assertRaises(TypeError): + issubclass(42, Any) + with self.assertRaises(TypeError): + Any[int] # Any is not a generic type. + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class A(Any): + pass + + def test_cannot_instantiate(self): + with self.assertRaises(TypeError): + Any() + + def test_cannot_subscript(self): + with self.assertRaises(TypeError): + Any[int] + + def test_any_is_subclass(self): + # Any should be considered a subclass of everything. + assert issubclass(Any, Any) + assert issubclass(Any, typing.List) + assert issubclass(Any, typing.List[int]) + assert issubclass(Any, typing.List[T]) + assert issubclass(Any, typing.Mapping) + assert issubclass(Any, typing.Mapping[str, int]) + assert issubclass(Any, typing.Mapping[KT, VT]) + assert issubclass(Any, Generic) + assert issubclass(Any, Generic[T]) + assert issubclass(Any, Generic[KT, VT]) + assert issubclass(Any, AnyStr) + assert issubclass(Any, Union) + assert issubclass(Any, Union[int, str]) + assert issubclass(Any, typing.Match) + assert issubclass(Any, typing.Match[str]) + # These expressions must simply not fail. + typing.Match[Any] + typing.Pattern[Any] + typing.IO[Any] + + +class TypeVarTests(TestCase): + + def test_basic_plain(self): + T = TypeVar('T') + # Nothing is an instance if T. + with self.assertRaises(TypeError): + isinstance('', T) + # Every class is a subclass of T. + assert issubclass(int, T) + assert issubclass(str, T) + # T equals itself. + assert T == T + # T is a subclass of itself. + assert issubclass(T, T) + + def test_basic_constrained(self): + A = TypeVar('A', str, bytes) + # Nothing is an instance of A. + with self.assertRaises(TypeError): + isinstance('', A) + # Only str and bytes are subclasses of A. + assert issubclass(str, A) + assert issubclass(bytes, A) + assert not issubclass(int, A) + # A equals itself. + assert A == A + # A is a subclass of itself. + assert issubclass(A, A) + + def test_constrained_error(self): + with self.assertRaises(TypeError): + X = TypeVar('X', int) + + def test_union_unique(self): + X = TypeVar('X') + Y = TypeVar('Y') + assert X != Y + assert Union[X] == X + assert Union[X] != Union[X, Y] + assert Union[X, X] == X + assert Union[X, int] != Union[X] + assert Union[X, int] != Union[int] + assert Union[X, int].__union_params__ == (X, int) + assert Union[X, int].__union_set_params__ == {X, int} + + def test_union_constrained(self): + A = TypeVar('A', str, bytes) + assert Union[A, str] != Union[A] + + def test_repr(self): + self.assertEqual(repr(T), '~T') + self.assertEqual(repr(KT), '~KT') + self.assertEqual(repr(VT), '~VT') + self.assertEqual(repr(AnyStr), '~AnyStr') + T_co = TypeVar('T_co', covariant=True) + self.assertEqual(repr(T_co), '+T_co') + T_contra = TypeVar('T_contra', contravariant=True) + self.assertEqual(repr(T_contra), '-T_contra') + + 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')): + pass + + def test_cannot_subclass_var_itself(self): + with self.assertRaises(TypeError): + class V(TypeVar): + pass + + def test_cannot_instantiate_vars(self): + with self.assertRaises(TypeError): + TypeVar('A')() + + def test_bound(self): + X = TypeVar('X', bound=Employee) + assert issubclass(Employee, X) + assert issubclass(Manager, X) + assert not issubclass(int, X) + + def test_bound_errors(self): + with self.assertRaises(TypeError): + TypeVar('X', bound=42) + with self.assertRaises(TypeError): + TypeVar('X', str, float, bound=Employee) + + +class UnionTests(TestCase): + + def test_basics(self): + u = Union[int, float] + self.assertNotEqual(u, Union) + self.assertIsInstance(42, u) + self.assertIsInstance(3.14, u) + self.assertTrue(issubclass(int, u)) + self.assertTrue(issubclass(float, u)) + + def test_union_any(self): + u = Union[Any] + self.assertEqual(u, Any) + u = Union[int, Any] + self.assertEqual(u, Any) + u = Union[Any, int] + self.assertEqual(u, Any) + + def test_union_object(self): + u = Union[object] + self.assertEqual(u, object) + u = Union[int, object] + self.assertEqual(u, object) + u = Union[object, int] + self.assertEqual(u, object) + + def test_union_any_object(self): + u = Union[object, Any] + self.assertEqual(u, Any) + u = Union[Any, object] + self.assertEqual(u, Any) + + def test_unordered(self): + u1 = Union[int, float] + u2 = Union[float, int] + self.assertEqual(u1, u2) + + def test_subclass(self): + u = Union[int, Employee] + self.assertIsInstance(Manager(), u) + 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.assertIsInstance(ManagingFounder(), u) + self.assertTrue(issubclass(ManagingFounder, u)) + + def test_single_class_disappears(self): + t = Union[Employee] + self.assertIs(t, Employee) + + def test_base_class_disappears(self): + u = Union[Employee, Manager, int] + self.assertEqual(u, Union[int, Employee]) + u = Union[Manager, int, Employee] + self.assertEqual(u, Union[int, Employee]) + 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] + self.assertEqual(v, Union[int, float, Employee]) + + def test_repr(self): + self.assertEqual(repr(Union), 'typing.Union') + u = Union[Employee, int] + self.assertEqual(repr(u), 'typing.Union[%s.Employee, int]' % __name__) + u = Union[int, Employee] + self.assertEqual(repr(u), 'typing.Union[int, %s.Employee]' % __name__) + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(Union): + pass + with self.assertRaises(TypeError): + class C(Union[int, str]): + pass + + def test_cannot_instantiate(self): + with self.assertRaises(TypeError): + Union() + u = Union[int, float] + with self.assertRaises(TypeError): + u() + + def test_optional(self): + o = Optional[int] + u = Union[int, None] + self.assertEqual(o, u) + self.assertIsInstance(42, o) + self.assertIsInstance(None, o) + self.assertNotIsInstance(3.14, o) + + def test_empty(self): + with self.assertRaises(TypeError): + Union[()] + + def test_issubclass_union(self): + assert issubclass(Union[int, str], Union) + assert not issubclass(int, Union) + + def test_isinstance_union(self): + # Nothing is an instance of bare Union. + assert not isinstance(42, Union) + assert not isinstance(int, Union) + assert not isinstance(Union[int, str], Union) + + +class TypeVarUnionTests(TestCase): + + def test_simpler(self): + A = TypeVar('A', int, str, float) + B = TypeVar('B', int, str) + assert issubclass(A, A) + assert issubclass(B, B) + assert not issubclass(B, A) + assert issubclass(A, Union[int, str, float]) + assert not issubclass(Union[int, str, float], A) + assert not issubclass(Union[int, str], B) + assert issubclass(B, Union[int, str]) + assert not issubclass(A, B) + assert not issubclass(Union[int, str, float], B) + assert not issubclass(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) + assert issubclass(int, TU) + assert issubclass(float, TU) + with self.assertRaises(TypeError): + isinstance(42, TU) + with self.assertRaises(TypeError): + isinstance('', TU) + + +class TupleTests(TestCase): + + def test_basics(self): + self.assertIsInstance((42, 3.14, ''), Tuple) + self.assertIsInstance((42, 3.14, ''), Tuple[int, float, str]) + self.assertIsInstance((42,), Tuple[int]) + self.assertNotIsInstance((3.14,), Tuple[int]) + self.assertNotIsInstance((42, 3.14), Tuple[int, float, str]) + self.assertNotIsInstance((42, 3.14, 100), Tuple[int, float, str]) + self.assertNotIsInstance((42, 3.14, 100), Tuple[int, float]) + 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])) + self.assertTrue(issubclass(tuple, Tuple)) + self.assertFalse(issubclass(Tuple, tuple)) # Can't have it both ways. + + def test_tuple_subclass(self): + class MyTuple(tuple): + pass + self.assertTrue(issubclass(MyTuple, Tuple)) + + def test_tuple_ellipsis(self): + t = Tuple[int, ...] + assert isinstance((), t) + assert isinstance((1,), t) + assert isinstance((1, 2), t) + assert isinstance((1, 2, 3), t) + assert not isinstance((3.14,), t) + assert not isinstance((1, 2, 3.14,), t) + + def test_tuple_ellipsis_subclass(self): + + class B: + pass + + class C(B): + pass + + assert not issubclass(Tuple[B], Tuple[B, ...]) + assert issubclass(Tuple[C, ...], Tuple[B, ...]) + assert not issubclass(Tuple[C, ...], Tuple[B]) + assert not issubclass(Tuple[C], Tuple[B, ...]) + + def test_repr(self): + self.assertEqual(repr(Tuple), 'typing.Tuple') + self.assertEqual(repr(Tuple[()]), 'typing.Tuple[]') + self.assertEqual(repr(Tuple[int, float]), 'typing.Tuple[int, float]') + self.assertEqual(repr(Tuple[int, ...]), 'typing.Tuple[int, ...]') + + def test_errors(self): + with self.assertRaises(TypeError): + issubclass(42, Tuple) + with self.assertRaises(TypeError): + issubclass(42, Tuple[int]) + + +class CallableTests(TestCase): + + def test_basics(self): + c = Callable[[int, float], str] + + def flub(a: int, b: float) -> str: + return str(a * b) + + def flob(a: int, b: int) -> str: + return str(a * b) + + self.assertIsInstance(flub, c) + self.assertNotIsInstance(flob, c) + + 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])) + + def test_eq_hash(self): + self.assertEqual(Callable[[int], int], Callable[[int], int]) + self.assertEqual(len({Callable[[int], int], Callable[[int], int]}), 1) + self.assertNotEqual(Callable[[int], int], Callable[[int], str]) + self.assertNotEqual(Callable[[int], int], Callable[[str], int]) + self.assertNotEqual(Callable[[int], int], Callable[[int, int], int]) + self.assertNotEqual(Callable[[int], int], Callable[[], int]) + self.assertNotEqual(Callable[[int], int], Callable) + + def test_with_none(self): + c = Callable[[None], None] + + def flub(self: None) -> None: + pass + + def flab(self: Any) -> None: + pass + + def flob(self: None) -> Any: + pass + + self.assertIsInstance(flub, c) + self.assertIsInstance(flab, c) + self.assertNotIsInstance(flob, c) # Test contravariance. + + def test_with_subclasses(self): + c = Callable[[Employee, Manager], Employee] + + def flub(a: Employee, b: Employee) -> Manager: + return Manager() + + def flob(a: Manager, b: Manager) -> Employee: + return Employee() + + self.assertIsInstance(flub, c) + self.assertNotIsInstance(flob, c) + + def test_with_default_args(self): + c = Callable[[int], int] + + def flub(a: int, b: float = 3.14) -> int: + return a + + def flab(a: int, *, b: float = 3.14) -> int: + return a + + def flob(a: int = 42) -> int: + return a + + self.assertIsInstance(flub, c) + self.assertIsInstance(flab, c) + self.assertIsInstance(flob, c) + + def test_with_varargs(self): + c = Callable[[int], int] + + def flub(*args) -> int: + return 42 + + def flab(*args: int) -> int: + return 42 + + def flob(*args: float) -> int: + return 42 + + self.assertIsInstance(flub, c) + self.assertIsInstance(flab, c) + self.assertNotIsInstance(flob, c) + + def test_with_method(self): + + class C: + + def imethod(self, arg: int) -> int: + self.last_arg = arg + return arg + 1 + + @classmethod + def cmethod(cls, arg: int) -> int: + cls.last_cls_arg = arg + return arg + 1 + + @staticmethod + def smethod(arg: int) -> int: + return arg + 1 + + ct = Callable[[int], int] + self.assertIsInstance(C().imethod, ct) + self.assertIsInstance(C().cmethod, ct) + self.assertIsInstance(C.cmethod, ct) + self.assertIsInstance(C().smethod, ct) + self.assertIsInstance(C.smethod, ct) + self.assertIsInstance(C.imethod, Callable[[Any, int], int]) + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + + class C(Callable): + pass + + with self.assertRaises(TypeError): + + class C(Callable[[int], int]): + pass + + def test_cannot_instantiate(self): + with self.assertRaises(TypeError): + Callable() + c = Callable[[int], str] + with self.assertRaises(TypeError): + c() + + def test_varargs(self): + ct = Callable[..., int] + + def foo(a, b) -> int: + return 42 + + def bar(a=42) -> int: + return a + + def baz(*, x, y, z) -> int: + return 100 + + self.assertIsInstance(foo, ct) + self.assertIsInstance(bar, ct) + self.assertIsInstance(baz, ct) + + def test_repr(self): + ct0 = Callable[[], bool] + self.assertEqual(repr(ct0), 'typing.Callable[[], bool]') + ct2 = Callable[[str, float], int] + self.assertEqual(repr(ct2), 'typing.Callable[[str, float], int]') + ctv = Callable[..., str] + self.assertEqual(repr(ctv), 'typing.Callable[..., str]') + + +XK = TypeVar('XK', str, bytes) +XV = TypeVar('XV') + + +class SimpleMapping(Generic[XK, XV]): + + def __getitem__(self, key: XK) -> XV: + ... + + def __setitem__(self, key: XK, value: XV): + ... + + def get(self, key: XK, default: XV = None) -> XV: + ... + + +class MySimpleMapping(SimpleMapping): + + def __init__(self): + self.store = {} + + def __getitem__(self, key: str): + return self.store[key] + + def __setitem__(self, key: str, value): + self.store[key] = value + + def get(self, key: str, default=None): + try: + return self.store[key] + except KeyError: + return default + + +class ProtocolTests(TestCase): + + def test_supports_int(self): + assert issubclass(int, typing.SupportsInt) + assert not issubclass(str, typing.SupportsInt) + + def test_supports_float(self): + assert issubclass(float, typing.SupportsFloat) + assert not issubclass(str, typing.SupportsFloat) + + def test_supports_complex(self): + + # Note: complex itself doesn't have __complex__. + class C: + def __complex__(self): + return 0j + + assert issubclass(C, typing.SupportsComplex) + assert not issubclass(str, typing.SupportsComplex) + + def test_supports_bytes(self): + + # Note: bytes itself doesn't have __bytes__. + class B: + def __bytes__(self): + return b'' + + assert issubclass(B, typing.SupportsBytes) + assert not issubclass(str, typing.SupportsBytes) + + def test_supports_abs(self): + assert issubclass(float, typing.SupportsAbs) + assert issubclass(int, typing.SupportsAbs) + assert not issubclass(str, typing.SupportsAbs) + + def test_supports_round(self): + assert issubclass(float, typing.SupportsRound) + assert issubclass(int, typing.SupportsRound) + assert not issubclass(str, typing.SupportsRound) + + def test_reversible(self): + assert issubclass(list, typing.Reversible) + assert not issubclass(int, typing.Reversible) + + +class GenericTests(TestCase): + + def test_basics(self): + X = SimpleMapping[str, Any] + Y = SimpleMapping[XK, str] + X[str, str] + Y[str, str] + with self.assertRaises(TypeError): + X[int, str] + with self.assertRaises(TypeError): + Y[str, bytes] + + def test_repr(self): + self.assertEqual(repr(SimpleMapping), + __name__ + '.' + 'SimpleMapping[~XK, ~XV]') + self.assertEqual(repr(MySimpleMapping), + __name__ + '.' + 'MySimpleMapping[~XK, ~XV]') + + def test_errors(self): + with self.assertRaises(TypeError): + B = SimpleMapping[XK, Any] + + class C(Generic[B]): + pass + + def test_repr_2(self): + PY32 = sys.version_info[:2] < (3, 3) + + class C(Generic[T]): + pass + + assert C.__module__ == __name__ + if not PY32: + assert C.__qualname__ == 'GenericTests.test_repr_2..C' + assert repr(C).split('.')[-1] == 'C[~T]' + X = C[int] + assert X.__module__ == __name__ + if not PY32: + assert X.__qualname__ == 'C' + assert repr(X).split('.')[-1] == 'C[int]' + + class Y(C[int]): + pass + + assert Y.__module__ == __name__ + if not PY32: + assert Y.__qualname__ == 'GenericTests.test_repr_2..Y' + assert repr(Y).split('.')[-1] == 'Y[int]' + + def test_eq_1(self): + assert Generic == Generic + assert Generic[T] == Generic[T] + assert Generic[KT] != Generic[VT] + + def test_eq_2(self): + + class A(Generic[T]): + pass + + class B(Generic[T]): + pass + + assert A == A + assert A != B + assert A[T] == A[T] + assert A[T] != B[T] + + def test_multiple_inheritance(self): + + class A(Generic[T, VT]): + pass + + class B(Generic[KT, T]): + pass + + class C(A, Generic[KT, VT], B): + pass + + assert C.__parameters__ == (T, VT, KT) + + def test_nested(self): + + class G(Generic): + pass + + class Visitor(G[T]): + + a = None + + def set(self, a: T): + self.a = a + + def get(self): + return self.a + + def visit(self) -> T: + return self.a + + V = Visitor[typing.List[int]] + + class IntListVisitor(V): + + def append(self, x: int): + self.a.append(x) + + a = IntListVisitor() + a.set([]) + a.append(1) + a.append(42) + assert a.get() == [1, 42] + + def test_type_erasure(self): + T = TypeVar('T') + + class Node(Generic[T]): + def __init__(self, label: T, left: 'Node[T]' = None, right: 'Node[T]' = None): + self.label = label # type: T + self.left = left # type: Optional[Node[T]] + self.right = right # type: Optional[Node[T]] + + def foo(x: T): + a = Node(x) + b = Node[T](x) + c = Node[Any](x) + assert type(a) is Node + assert type(b) is Node + assert type(c) is Node + + foo(42) + + +class VarianceTests(TestCase): + + def test_invariance(self): + # Because of invariance, List[subclass of X] is not a subclass + # of List[X], and ditto for MutableSequence. + assert not issubclass(typing.List[Manager], typing.List[Employee]) + assert not issubclass(typing.MutableSequence[Manager], + typing.MutableSequence[Employee]) + # It's still reflexive. + assert issubclass(typing.List[Employee], typing.List[Employee]) + assert issubclass(typing.MutableSequence[Employee], + typing.MutableSequence[Employee]) + + def test_covariance_tuple(self): + # Check covariace for Tuple (which are really special cases). + assert issubclass(Tuple[Manager], Tuple[Employee]) + assert not issubclass(Tuple[Employee], Tuple[Manager]) + # And pairwise. + assert issubclass(Tuple[Manager, Manager], Tuple[Employee, Employee]) + assert not issubclass(Tuple[Employee, Employee], + Tuple[Manager, Employee]) + # And using ellipsis. + assert issubclass(Tuple[Manager, ...], Tuple[Employee, ...]) + assert not issubclass(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 covariant type variable). + assert issubclass(typing.Sequence[Manager], typing.Sequence[Employee]) + assert not issubclass(typing.Sequence[Employee], + typing.Sequence[Manager]) + + def test_covariance_mapping(self): + # Ditto for Mapping (a generic class with two parameters). + assert issubclass(typing.Mapping[Employee, Manager], + typing.Mapping[Employee, Employee]) + assert issubclass(typing.Mapping[Manager, Employee], + typing.Mapping[Employee, Employee]) + assert not issubclass(typing.Mapping[Employee, Manager], + typing.Mapping[Manager, Manager]) + assert not issubclass(typing.Mapping[Manager, Employee], + typing.Mapping[Manager, Manager]) + + +class CastTests(TestCase): + + def test_basics(self): + assert cast(int, 42) == 42 + assert cast(float, 42) == 42 + assert type(cast(float, 42)) is int + assert cast(Any, 42) == 42 + assert cast(list, 42) == 42 + assert cast(Union[str, float], 42) == 42 + assert cast(AnyStr, 42) == 42 + assert cast(None, 42) == 42 + + def test_errors(self): + # Bogus calls are not expected to fail. + cast(42, 42) + cast('hello', 42) + + +class ForwardRefTests(TestCase): + + def test_basics(self): + + class Node(Generic[T]): + + def __init__(self, label: T): + self.label = label + self.left = self.right = None + + def add_both(self, + left: 'Optional[Node[T]]', + right: 'Node[T]' = None, + stuff: int = None, + blah=None): + self.left = left + self.right = right + + def add_left(self, node: Optional['Node[T]']): + self.add_both(node, None) + + def add_right(self, node: 'Node[T]' = None): + self.add_both(None, node) + + t = Node[int] + both_hints = get_type_hints(t.add_both, globals(), locals()) + assert both_hints['left'] == both_hints['right'] == Optional[Node[T]] + assert both_hints['stuff'] == Optional[int] + assert 'blah' not in both_hints + + left_hints = get_type_hints(t.add_left, globals(), locals()) + assert left_hints['node'] == Optional[Node[T]] + + right_hints = get_type_hints(t.add_right, globals(), locals()) + assert right_hints['node'] == Optional[Node[T]] + + def test_union_forward(self): + + def foo(a: Union['T']): + pass + + self.assertEqual(get_type_hints(foo, globals(), locals()), + {'a': Union[T]}) + + def test_tuple_forward(self): + + def foo(a: Tuple['T']): + pass + + self.assertEqual(get_type_hints(foo, globals(), locals()), + {'a': Tuple[T]}) + + def test_callable_forward(self): + + def foo(a: Callable[['T'], 'T']): + pass + + self.assertEqual(get_type_hints(foo, globals(), locals()), + {'a': Callable[[T], T]}) + + def test_syntax_error(self): + + with self.assertRaises(SyntaxError): + Generic['/T'] + + def test_delayed_syntax_error(self): + + def foo(a: 'Node[T'): + pass + + with self.assertRaises(SyntaxError): + get_type_hints(foo) + + def test_type_error(self): + + def foo(a: Tuple['42']): + pass + + with self.assertRaises(TypeError): + get_type_hints(foo) + + def test_name_error(self): + + def foo(a: 'Noode[T]'): + pass + + with self.assertRaises(NameError): + get_type_hints(foo, locals()) + + def test_no_type_check(self): + + @no_type_check + def foo(a: 'whatevers') -> {}: + pass + + th = get_type_hints(foo) + self.assertEqual(th, {}) + + def test_no_type_check_class(self): + + @no_type_check + class C: + def foo(a: 'whatevers') -> {}: + pass + + cth = get_type_hints(C.foo) + self.assertEqual(cth, {}) + ith = get_type_hints(C().foo) + self.assertEqual(ith, {}) + + def test_meta_no_type_check(self): + + @no_type_check_decorator + def magic_decorator(deco): + return deco + + self.assertEqual(magic_decorator.__name__, 'magic_decorator') + + @magic_decorator + def foo(a: 'whatevers') -> {}: + pass + + @magic_decorator + class C: + def foo(a: 'whatevers') -> {}: + pass + + self.assertEqual(foo.__name__, 'foo') + th = get_type_hints(foo) + self.assertEqual(th, {}) + cth = get_type_hints(C.foo) + self.assertEqual(cth, {}) + ith = get_type_hints(C().foo) + self.assertEqual(ith, {}) + + def test_default_globals(self): + code = ("class C:\n" + " def foo(self, a: 'C') -> 'D': pass\n" + "class D:\n" + " def bar(self, b: 'D') -> C: pass\n" + ) + ns = {} + exec(code, ns) + hints = get_type_hints(ns['C'].foo) + assert hints == {'a': ns['C'], 'return': ns['D']} + + +class OverloadTests(TestCase): + + def test_overload_exists(self): + from typing import overload + + def test_overload_fails(self): + from typing import overload + + with self.assertRaises(RuntimeError): + @overload + def blah(): + pass + + +class CollectionsAbcTests(TestCase): + + def test_hashable(self): + assert isinstance(42, typing.Hashable) + assert not isinstance([], typing.Hashable) + + def test_iterable(self): + assert isinstance([], typing.Iterable) + assert isinstance([], typing.Iterable[int]) + assert not isinstance(42, typing.Iterable) + + def test_iterator(self): + it = iter([]) + assert isinstance(it, typing.Iterator) + assert isinstance(it, typing.Iterator[int]) + assert not isinstance(42, typing.Iterator) + + def test_sized(self): + assert isinstance([], typing.Sized) + assert not isinstance(42, typing.Sized) + + def test_container(self): + assert isinstance([], typing.Container) + assert not isinstance(42, typing.Container) + + def test_abstractset(self): + assert isinstance(set(), typing.AbstractSet) + assert not isinstance(42, typing.AbstractSet) + + def test_mutableset(self): + assert isinstance(set(), typing.MutableSet) + assert not isinstance(frozenset(), typing.MutableSet) + + def test_mapping(self): + assert isinstance({}, typing.Mapping) + assert not isinstance(42, typing.Mapping) + + def test_mutablemapping(self): + assert isinstance({}, typing.MutableMapping) + assert not isinstance(42, typing.MutableMapping) + + def test_sequence(self): + assert isinstance([], typing.Sequence) + assert not isinstance(42, typing.Sequence) + + def test_mutablesequence(self): + assert isinstance([], typing.MutableSequence) + assert not isinstance((), typing.MutableSequence) + + def test_bytestring(self): + assert isinstance(b'', typing.ByteString) + assert isinstance(bytearray(b''), typing.ByteString) + + def test_list(self): + assert issubclass(list, typing.List) + assert isinstance([], typing.List) + assert not isinstance((), typing.List) + t = typing.List[int] + assert isinstance([], t) + assert isinstance([42], t) + assert not isinstance([''], t) + + def test_set(self): + assert issubclass(set, typing.Set) + assert not issubclass(frozenset, typing.Set) + assert isinstance(set(), typing.Set) + assert not isinstance({}, typing.Set) + t = typing.Set[int] + assert isinstance(set(), t) + assert isinstance({42}, t) + assert not isinstance({''}, t) + + def test_frozenset(self): + assert issubclass(frozenset, typing.FrozenSet) + assert not issubclass(set, typing.FrozenSet) + assert isinstance(frozenset(), typing.FrozenSet) + assert not isinstance({}, typing.FrozenSet) + t = typing.FrozenSet[int] + assert isinstance(frozenset(), t) + assert isinstance(frozenset({42}), t) + assert not isinstance(frozenset({''}), t) + assert not isinstance({42}, t) + + def test_mapping_views(self): + # TODO: These tests are kind of lame. + assert isinstance({}.keys(), typing.KeysView) + assert isinstance({}.items(), typing.ItemsView) + assert isinstance({}.values(), typing.ValuesView) + + def test_dict(self): + assert issubclass(dict, typing.Dict) + assert isinstance({}, typing.Dict) + assert not isinstance([], typing.Dict) + t = typing.Dict[int, str] + assert isinstance({}, t) + assert isinstance({42: ''}, t) + assert not isinstance({42: 42}, t) + assert not isinstance({'': 42}, t) + assert not isinstance({'': ''}, t) + + def test_no_list_instantiation(self): + with self.assertRaises(TypeError): + typing.List() + with self.assertRaises(TypeError): + typing.List[T]() + with self.assertRaises(TypeError): + typing.List[int]() + + def test_list_subclass_instantiation(self): + + class MyList(typing.List[int]): + pass + + a = MyList() + assert isinstance(a, MyList) + + def test_no_dict_instantiation(self): + with self.assertRaises(TypeError): + typing.Dict() + with self.assertRaises(TypeError): + typing.Dict[KT, VT]() + with self.assertRaises(TypeError): + typing.Dict[str, int]() + + def test_dict_subclass_instantiation(self): + + class MyDict(typing.Dict[str, int]): + pass + + d = MyDict() + assert isinstance(d, MyDict) + + def test_no_set_instantiation(self): + with self.assertRaises(TypeError): + typing.Set() + with self.assertRaises(TypeError): + typing.Set[T]() + with self.assertRaises(TypeError): + typing.Set[int]() + + def test_set_subclass_instantiation(self): + + class MySet(typing.Set[int]): + pass + + d = MySet() + assert isinstance(d, MySet) + + def test_no_frozenset_instantiation(self): + with self.assertRaises(TypeError): + typing.FrozenSet() + with self.assertRaises(TypeError): + typing.FrozenSet[T]() + with self.assertRaises(TypeError): + typing.FrozenSet[int]() + + def test_frozenset_subclass_instantiation(self): + + class MyFrozenSet(typing.FrozenSet[int]): + pass + + d = MyFrozenSet() + assert isinstance(d, MyFrozenSet) + + def test_no_tuple_instantiation(self): + with self.assertRaises(TypeError): + Tuple() + with self.assertRaises(TypeError): + Tuple[T]() + with self.assertRaises(TypeError): + Tuple[int]() + + def test_generator(self): + def foo(): + yield 42 + g = foo() + assert issubclass(type(g), typing.Generator) + assert isinstance(g, typing.Generator) + assert not isinstance(foo, typing.Generator) + assert issubclass(typing.Generator[Manager, Employee, Manager], + typing.Generator[Employee, Manager, Employee]) + assert not issubclass(typing.Generator[Manager, Manager, Manager], + typing.Generator[Employee, Employee, Employee]) + + def test_no_generator_instantiation(self): + with self.assertRaises(TypeError): + typing.Generator() + with self.assertRaises(TypeError): + typing.Generator[T, T, T]() + with self.assertRaises(TypeError): + typing.Generator[int, int, int]() + + def test_subclassing(self): + + class MMA(typing.MutableMapping): + pass + + with self.assertRaises(TypeError): # It's abstract + MMA() + + class MMC(MMA): + def __len__(self): + return 0 + + assert len(MMC()) == 0 + + class MMB(typing.MutableMapping[KT, VT]): + def __len__(self): + return 0 + + assert len(MMB()) == 0 + assert len(MMB[str, str]()) == 0 + assert len(MMB[KT, VT]()) == 0 + + def test_recursive_dict(self): + D = typing.Dict[int, 'D'] # Uses a _ForwardRef + assert isinstance({}, D) # Easy + assert isinstance({0: {}}, D) # Touches _ForwardRef + assert isinstance({0: {0: {}}}, D) # Etc... + + +class NamedTupleTests(TestCase): + + def test_basics(self): + Emp = NamedTuple('Emp', [('name', str), ('id', int)]) + assert issubclass(Emp, tuple) + joe = Emp('Joe', 42) + jim = Emp(name='Jim', id=1) + assert isinstance(joe, Emp) + assert isinstance(joe, tuple) + assert joe.name == 'Joe' + assert joe.id == 42 + assert jim.name == 'Jim' + assert jim.id == 1 + assert Emp.__name__ == 'Emp' + assert Emp._fields == ('name', 'id') + assert Emp._field_types == dict(name=str, id=int) + + +class IOTests(TestCase): + + def test_io(self): + + def stuff(a: IO) -> AnyStr: + return a.readline() + + a = stuff.__annotations__['a'] + assert a.__parameters__ == (AnyStr,) + + def test_textio(self): + + def stuff(a: TextIO) -> str: + return a.readline() + + a = stuff.__annotations__['a'] + assert a.__parameters__ == (str,) + + def test_binaryio(self): + + def stuff(a: BinaryIO) -> bytes: + return a.readline() + + a = stuff.__annotations__['a'] + assert a.__parameters__ == (bytes,) + + def test_io_submodule(self): + from typing.io import IO, TextIO, BinaryIO, __all__, __name__ + assert IO is typing.IO + assert TextIO is typing.TextIO + assert BinaryIO is typing.BinaryIO + assert set(__all__) == set(['IO', 'TextIO', 'BinaryIO']) + assert __name__ == 'typing.io' + + +class RETests(TestCase): + # Much of this is really testing _TypeAlias. + + def test_basics(self): + pat = re.compile('[a-z]+', re.I) + assert issubclass(pat.__class__, Pattern) + assert isinstance(pat, Pattern[str]) + assert not isinstance(pat, Pattern[bytes]) + assert issubclass(type(pat), Pattern) + assert issubclass(type(pat), Pattern[str]) + + mat = pat.search('12345abcde.....') + assert issubclass(mat.__class__, Match) + assert issubclass(mat.__class__, Match[str]) + assert issubclass(mat.__class__, Match[bytes]) # Sad but true. + assert issubclass(type(mat), Match) + assert issubclass(type(mat), Match[str]) + + p = Pattern[Union[str, bytes]] + assert isinstance(pat, p) + assert issubclass(Pattern[str], Pattern) + assert issubclass(Pattern[str], p) + + m = Match[Union[bytes, str]] + assert isinstance(mat, m) + assert issubclass(Match[bytes], Match) + assert issubclass(Match[bytes], m) + + def test_errors(self): + with self.assertRaises(TypeError): + # Doesn't fit AnyStr. + Pattern[int] + with self.assertRaises(TypeError): + # Can't change type vars? + Match[T] + m = Match[Union[str, bytes]] + with self.assertRaises(TypeError): + # Too complicated? + m[str] + + def test_repr(self): + assert repr(Pattern) == 'Pattern[~AnyStr]' + assert repr(Pattern[str]) == 'Pattern[str]' + assert repr(Pattern[bytes]) == 'Pattern[bytes]' + assert repr(Match) == 'Match[~AnyStr]' + assert repr(Match[str]) == 'Match[str]' + assert repr(Match[bytes]) == 'Match[bytes]' + + def test_re_submodule(self): + from typing.re import Match, Pattern, __all__, __name__ + assert Match is typing.Match + assert Pattern is typing.Pattern + assert set(__all__) == set(['Match', 'Pattern']) + assert __name__ == 'typing.re' + + def test_cannot_subclass(self): + with self.assertRaises(TypeError) as ex: + + class A(typing.Match): + pass + + assert str(ex.exception) == "A type alias cannot be subclassed" + + +class AllTests(TestCase): + """Tests for __all__.""" + + def test_all(self): + from typing import __all__ as a + # Just spot-check the first and last of every category. + assert 'AbstractSet' in a + assert 'ValuesView' in a + assert 'cast' in a + assert 'overload' in a + assert 'io' in a + assert 're' in a + # Spot-check that stdlib modules aren't exported. + assert 'os' not in a + assert 'sys' not in a + + +if __name__ == '__main__': + main() diff --git a/Lib/typing.py b/Lib/typing.py new file mode 100644 index 00000000000..38e07ad50b7 --- /dev/null +++ b/Lib/typing.py @@ -0,0 +1,1714 @@ +# TODO: +# - Generic[T, T] is invalid +# - Look for TODO below + +# TODO nits: +# Get rid of asserts that are the caller's fault. +# Docstrings (e.g. ABCs). + +import abc +from abc import abstractmethod, abstractproperty +import collections +import functools +import re as stdlib_re # Avoid confusion with the re we export. +import sys +import types +try: + import collections.abc as collections_abc +except ImportError: + import collections as collections_abc # Fallback for PY3.2. + + +# Please keep __all__ alphabetized within each category. +__all__ = [ + # Super-special typing primitives. + 'Any', + 'Callable', + 'Generic', + 'Optional', + 'TypeVar', + 'Union', + 'Tuple', + + # ABCs (from collections.abc). + 'AbstractSet', # collections.abc.Set. + 'ByteString', + 'Container', + 'Hashable', + 'ItemsView', + 'Iterable', + 'Iterator', + 'KeysView', + 'Mapping', + 'MappingView', + 'MutableMapping', + 'MutableSequence', + 'MutableSet', + 'Sequence', + 'Sized', + 'ValuesView', + + # Structural checks, a.k.a. protocols. + 'Reversible', + 'SupportsAbs', + 'SupportsFloat', + 'SupportsInt', + 'SupportsRound', + + # Concrete collection types. + 'Dict', + 'List', + 'Set', + 'NamedTuple', # Not really a type. + 'Generator', + + # One-off things. + 'AnyStr', + 'cast', + 'get_type_hints', + 'no_type_check', + 'no_type_check_decorator', + 'overload', + + # Submodules. + 'io', + 're', +] + + +def _qualname(x): + if sys.version_info[:2] >= (3, 3): + return x.__qualname__ + else: + # Fall back to just name. + return x.__name__ + + +class TypingMeta(type): + """Metaclass for every type defined below. + + This overrides __new__() to require an extra keyword parameter + '_root', which serves as a guard against naive subclassing of the + typing classes. Any legitimate class defined using a metaclass + derived from TypingMeta (including internal subclasses created by + e.g. Union[X, Y]) must pass _root=True. + + This also defines a dummy constructor (all the work is done in + __new__) and a nicer repr(). + """ + + _is_protocol = False + + def __new__(cls, name, bases, namespace, *, _root=False): + if not _root: + raise TypeError("Cannot subclass %s" % + (', '.join(map(_type_repr, bases)) or '()')) + return super().__new__(cls, name, bases, namespace) + + def __init__(self, *args, **kwds): + pass + + def _eval_type(self, globalns, localns): + """Override this in subclasses to interpret forward references. + + For example, Union['C'] is internally stored as + Union[_ForwardRef('C')], which should evaluate to _Union[C], + where C is an object found in globalns or localns (searching + localns first, of course). + """ + return self + + def _has_type_var(self): + return False + + def __repr__(self): + return '%s.%s' % (self.__module__, _qualname(self)) + + +class Final: + """Mix-in class to prevent instantiation.""" + + def __new__(self, *args, **kwds): + raise TypeError("Cannot instantiate %r" % self.__class__) + + +class _ForwardRef(TypingMeta): + """Wrapper to hold a forward reference.""" + + def __new__(cls, arg): + if not isinstance(arg, str): + raise TypeError('ForwardRef must be a string -- got %r' % (arg,)) + try: + code = compile(arg, '', 'eval') + 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 + self.__forward_value__ = None + typing_globals = globals() + frame = sys._getframe(1) + while frame is not None and frame.f_globals is typing_globals: + frame = frame.f_back + assert frame is not None + self.__forward_frame__ = frame + return self + + def _eval_type(self, globalns, localns): + if not isinstance(localns, dict): + raise TypeError('ForwardRef localns must be a dict -- got %r' % + (localns,)) + if not isinstance(globalns, dict): + raise TypeError('ForwardRef globalns must be a dict -- got %r' % + (globalns,)) + if not self.__forward_evaluated__: + if globalns is None and localns is None: + globalns = localns = {} + elif globalns is None: + globalns = localns + elif localns is None: + localns = globalns + self.__forward_value__ = _type_check( + eval(self.__forward_code__, globalns, localns), + "Forward references must evaluate to types.") + self.__forward_evaluated__ = True + return self.__forward_value__ + + 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__) + + def __instancecheck__(self, obj): + 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 isinstance(obj, self.__forward_value__) + + def __repr__(self): + return '_ForwardRef(%r)' % (self.__forward_arg__,) + + +class _TypeAlias: + """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. + """ + + 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. + + Args: + name: The name, e.g. 'Pattern'. + type_var: The type parameter, e.g. AnyStr, or the + specific type, e.g. str. + impl_type: The implementation type. + type_checker: Function that takes an impl_type instance. + 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.impl_type = impl_type + self.type_checker = type_checker + + 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__]): + 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) + + def __instancecheck__(self, obj): + return (isinstance(obj, self.impl_type) and + isinstance(self.type_checker(obj), self.type_var)) + + 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) + + +def _has_type_var(t): + return t is not None and isinstance(t, TypingMeta) and t._has_type_var() + + +def _eval_type(t, globalns, localns): + if isinstance(t, TypingMeta): + return t._eval_type(globalns, localns) + else: + return t + + +def _type_check(arg, msg): + """Check that the argument is a type, and return it. + + As a special case, accept None and return type(None) instead. + Also, _TypeAlias instances (e.g. Match, Pattern) are acceptable. + + The msg argument is a human-readable error message, e.g. + + "Union[arg, ...]: arg should be a type." + + We append the repr() of the actual value (truncated to 100 chars). + """ + if arg is None: + return type(None) + if isinstance(arg, str): + arg = _ForwardRef(arg) + if not isinstance(arg, (type, _TypeAlias)): + raise TypeError(msg + " Got %.100r." % (arg,)) + return arg + + +def _type_repr(obj): + """Return the repr() of an object, special-casing types. + + If obj is a type, we return a shorter version than the default + type.__repr__, based on the module and qualified name, which is + typically enough to uniquely identify a type. For everything + else, we fall back on repr(obj). + """ + if isinstance(obj, type) and not isinstance(obj, TypingMeta): + if obj.__module__ == 'builtins': + return _qualname(obj) + else: + return '%s.%s' % (obj.__module__, _qualname(obj)) + else: + return repr(obj) + + +class AnyMeta(TypingMeta): + """Metaclass for Any.""" + + def __new__(cls, name, bases, namespace, _root=False): + self = super().__new__(cls, name, bases, namespace, _root=_root) + return self + + def __instancecheck__(self, instance): + return True + + def __subclasscheck__(self, cls): + if not isinstance(cls, type): + return super().__subclasscheck__(cls) # To TypeError. + return True + + +class Any(Final, metaclass=AnyMeta, _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. + """ + + +class TypeVar(TypingMeta, metaclass=TypingMeta, _root=True): + """Type variable. + + Usage:: + + T = TypeVar('T') # Can be anything + A = TypeVar('A', str, bytes) # Must be str or bytes + + Type variables exist primarily for the benefit of static type + checkers. They serve as the parameters for generic types as well + as for generic function definitions. See class Generic for more + information on generic types. Generic functions work as follows: + + def repeat(x: T, n: int) -> Sequence[T]: + '''Return a list containing n references to x.''' + return [x]*n + + def longest(x: A, y: A) -> A: + '''Return the longest of two strings.''' + return x if len(x) >= len(y) else y + + The latter example's signature is essentially the overloading + of (str, str) -> str and (bytes, bytes) -> bytes. Also note + that if the arguments are instances of some subclass of str, + the return type is still plain str. + + At runtime, isinstance(x, T) will raise TypeError. However, + issubclass(C, T) is true for any class C, and issubclass(str, A) + and issubclass(bytes, A) are true, and issubclass(int, A) is + false. + + Type variables may be marked covariant or contravariant by passing + covariant=True or contravariant=True. See PEP 484 for more + details. By default type variables are invariant. + + Type variables can be introspected. e.g.: + + T.__name__ == 'T' + T.__constraints__ == () + T.__covariant__ == False + T.__contravariant__ = False + A.__constraints__ == (str, bytes) + """ + + def __new__(cls, name, *constraints, bound=None, + covariant=False, contravariant=False): + self = super().__new__(cls, name, (Final,), {}, _root=True) + if covariant and contravariant: + raise ValueError("Bivariant type variables are not supported.") + self.__covariant__ = bool(covariant) + self.__contravariant__ = bool(contravariant) + if constraints and bound is not None: + raise TypeError("Constraints cannot be combined with bound=...") + if constraints and len(constraints) == 1: + raise TypeError("A single constraint is not allowed") + msg = "TypeVar(name, constraint, ...): constraints must be types." + self.__constraints__ = tuple(_type_check(t, msg) for t in constraints) + if bound: + self.__bound__ = _type_check(bound, "Bound must be a type.") + else: + self.__bound__ = None + return self + + def _has_type_var(self): + return True + + def __repr__(self): + if self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ + + 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 + + +# Some unconstrained type variables. These are used by the container types. +T = TypeVar('T') # Any type. +KT = TypeVar('KT') # Key type. +VT = TypeVar('VT') # Value type. +T_co = TypeVar('T_co', covariant=True) # Any type covariant containers. +V_co = TypeVar('V_co', covariant=True) # Any type covariant containers. +KT_co = TypeVar('KT_co', covariant=True) # Key type covariant containers. +VT_co = TypeVar('VT_co', covariant=True) # Value type covariant containers. +T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant. + +# A useful type variable with constraints. This represents string types. +# TODO: What about bytearray, memoryview? +AnyStr = TypeVar('AnyStr', bytes, str) + + +class UnionMeta(TypingMeta): + """Metaclass for Union.""" + + def __new__(cls, name, bases, namespace, parameters=None, _root=False): + if parameters is None: + return super().__new__(cls, name, bases, namespace, _root=_root) + 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): + params.extend(p.__union_params__) + else: + params.append(_type_check(p, msg)) + # Weed out strict duplicates, preserving the first of each occurrence. + all_params = set(params) + if len(all_params) < len(params): + new_params = [] + for t in params: + if t in all_params: + new_params.append(t) + all_params.remove(t) + params = new_params + assert not all_params, all_params + # Weed out subclasses. + # E.g. Union[int, Employee, Manager] == Union[int, Employee]. + # If Any or object is present it will be the sole survivor. + # If both Any and object are present, Any wins. + # Never discard type variables, except against Any. + # (In particular, Union[str, AnyStr] != AnyStr.) + all_params = set(params) + for t1 in params: + if t1 is Any: + return Any + if isinstance(t1, TypeVar): + continue + if any(issubclass(t1, t2) + for t2 in all_params - {t1} if not isinstance(t2, TypeVar)): + all_params.remove(t1) + # It's not a union if there's only one type left. + if len(all_params) == 1: + return all_params.pop() + # Create a new class with these params. + self = super().__new__(cls, name, bases, {}, _root=True) + self.__union_params__ = tuple(t for t in params if t in all_params) + self.__union_set_params__ = frozenset(self.__union_params__) + return self + + def _eval_type(self, globalns, localns): + p = tuple(_eval_type(t, globalns, localns) + for t in self.__union_params__) + if p == self.__union_params__: + return self + else: + return self.__class__(self.__name__, self.__bases__, {}, + p, _root=True) + + def _has_type_var(self): + if self.__union_params__: + for t in self.__union_params__: + if _has_type_var(t): + return True + return False + + def __repr__(self): + r = super().__repr__() + if self.__union_params__: + r += '[%s]' % (', '.join(_type_repr(t) + for t in self.__union_params__)) + return r + + def __getitem__(self, parameters): + if self.__union_params__ is not None: + raise TypeError( + "Cannot subscript an existing Union. Use Union[u, t] instead.") + if 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) + + def __eq__(self, other): + if not isinstance(other, UnionMeta): + return NotImplemented + return self.__union_set_params__ == other.__union_set_params__ + + def __hash__(self): + return hash(self.__union_set_params__) + + def __instancecheck__(self, instance): + return (self.__union_set_params__ is not None and + any(isinstance(instance, t) for t in self.__union_params__)) + + 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.:: + + 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 + + +class OptionalMeta(TypingMeta): + """Metaclass for Optional.""" + + def __new__(cls, name, bases, namespace, _root=False): + return super().__new__(cls, name, bases, namespace, _root=_root) + + 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[X] is equivalent to Union[X, type(None)]. + """ + + +class TupleMeta(TypingMeta): + """Metaclass for Tuple.""" + + def __new__(cls, name, bases, namespace, 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 _has_type_var(self): + if self.__tuple_params__: + for t in self.__tuple_params__: + if _has_type_var(t): + return True + return False + + def _eval_type(self, globalns, localns): + tp = self.__tuple_params__ + if tp is None: + return self + p = tuple(_eval_type(t, globalns, localns) for t in tp) + if p == self.__tuple_params__: + return self + else: + return self.__class__(self.__name__, self.__bases__, {}, + p, _root=True) + + def __repr__(self): + 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__: + params.append('...') + r += '[%s]' % ( + ', '.join(params)) + return r + + def __getitem__(self, parameters): + if self.__tuple_params__ is not None: + raise TypeError("Cannot re-parameterize %r" % (self,)) + if not isinstance(parameters, tuple): + parameters = (parameters,) + if len(parameters) == 2 and parameters[1] == Ellipsis: + parameters = parameters[:1] + use_ellipsis = True + msg = "Tuple[t, ...]: t must be a type." + else: + 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, _root=True) + + def __eq__(self, other): + if not isinstance(other, TupleMeta): + return NotImplemented + return self.__tuple_params__ == other.__tuple_params__ + + def __hash__(self): + return hash(self.__tuple_params__) + + def __instancecheck__(self, t): + if not isinstance(t, tuple): + return False + if self.__tuple_params__ is None: + return True + if self.__tuple_use_ellipsis__: + p = self.__tuple_params__[0] + return all(isinstance(x, p) for x in t) + else: + return (len(t) == len(self.__tuple_params__) and + all(isinstance(x, p) + for x, p in zip(t, self.__tuple_params__))) + + def __subclasscheck__(self, cls): + if cls is Any: + return True + if not isinstance(cls, type): + return super().__subclasscheck__(cls) # To TypeError. + if issubclass(cls, tuple): + return True # Special case. + if not isinstance(cls, TupleMeta): + return super().__subclasscheck__(cls) # False. + if self.__tuple_params__ is None: + return True + if cls.__tuple_params__ is None: + return False # ??? + if cls.__tuple_use_ellipsis__ != self.__tuple_use_ellipsis__: + return False + # Covariance. + return (len(self.__tuple_params__) == len(cls.__tuple_params__) and + all(issubclass(x, p) + for x, p in zip(cls.__tuple_params__, + self.__tuple_params__))) + + +class Tuple(Final, metaclass=TupleMeta, _root=True): + """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. + + 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]. + """ + + +class CallableMeta(TypingMeta): + """Metaclass for Callable.""" + + def __new__(cls, name, bases, namespace, _root=False, + args=None, result=None): + if args is None and result is None: + pass # Must be 'class Callable'. + else: + if args is not Ellipsis: + if not isinstance(args, list): + raise TypeError("Callable[args, result]: " + "args must be a list." + " Got %.100r." % (args,)) + msg = "Callable[[arg, ...], result]: each arg must be a type." + 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 _has_type_var(self): + if self.__args__: + for t in self.__args__: + if _has_type_var(t): + return True + return _has_type_var(self.__result__) + + def _eval_type(self, globalns, localns): + if self.__args__ is None and self.__result__ is None: + return self + args = [_eval_type(t, globalns, localns) for t in self.__args__] + result = _eval_type(self.__result__, 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) + + def __repr__(self): + r = super().__repr__() + if self.__args__ is not None or self.__result__ is not None: + if self.__args__ is Ellipsis: + args_r = '...' + else: + args_r = '[%s]' % ', '.join(_type_repr(t) + for t in self.__args__) + r += '[%s, %s]' % (args_r, _type_repr(self.__result__)) + return r + + def __getitem__(self, parameters): + if self.__args__ is not None or self.__result__ is not None: + raise TypeError("This Callable type is already parameterized.") + if not isinstance(parameters, tuple) or len(parameters) != 2: + 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) + + def __eq__(self, other): + if not isinstance(other, CallableMeta): + return NotImplemented + return (self.__args__ == other.__args__ and + self.__result__ == other.__result__) + + def __hash__(self): + return hash(self.__args__) ^ hash(self.__result__) + + def __instancecheck__(self, instance): + if not callable(instance): + return False + if self.__args__ is None and self.__result__ is None: + return True + assert self.__args__ is not None + assert self.__result__ is not None + my_args, my_result = self.__args__, self.__result__ + import inspect # TODO: Avoid this import. + # Would it be better to use Signature objects? + try: + (args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, + annotations) = inspect.getfullargspec(instance) + except TypeError: + return False # We can't find the signature. Give up. + msg = ("When testing isinstance(, Callable[...], " + "'s annotations must be types.") + if my_args is not Ellipsis: + if kwonlyargs and (not kwonlydefaults or + len(kwonlydefaults) < len(kwonlyargs)): + return False + if isinstance(instance, types.MethodType): + # For methods, getfullargspec() includes self/cls, + # but it's not part of the call signature, so drop it. + del args[0] + min_call_args = len(args) + if defaults: + min_call_args -= len(defaults) + if varargs: + max_call_args = 999999999 + if len(args) < len(my_args): + args += [varargs] * (len(my_args) - len(args)) + else: + max_call_args = len(args) + if not min_call_args <= len(my_args) <= max_call_args: + return False + for my_arg_type, name in zip(my_args, args): + if name in annotations: + annot_type = _type_check(annotations[name], msg) + else: + annot_type = Any + if not issubclass(my_arg_type, annot_type): + return False + # TODO: If mutable type, check invariance? + if 'return' in annotations: + annot_return_type = _type_check(annotations['return'], msg) + # Note contravariance here! + if not issubclass(annot_return_type, my_result): + return False + # Can't find anything wrong... + return True + + 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. + + There is no syntax to indicate optional or keyword arguments, + such function types are rarely used as callback types. + """ + + +def _gorg(a): + """Return the farthest origin of a generic class.""" + assert isinstance(a, GenericMeta) + while a.__origin__ is not None: + a = a.__origin__ + return a + + +def _geqv(a, b): + """Return whether two generic classes are equivalent. + + The intention is to consider generic class X and any of its + parameterized forms (X[T], X[int], etc.) as equivalent. + + However, X is not equivalent to a subclass of X. + + The relation is reflexive, symmetric and transitive. + """ + assert isinstance(a, GenericMeta) and isinstance(b, GenericMeta) + # Reduce each to its origin. + return _gorg(a) is _gorg(b) + + +class GenericMeta(TypingMeta, abc.ABCMeta): + """Metaclass for generic types.""" + + # TODO: Constrain more how Generic is used; only a few + # standard patterns should be allowed. + + # TODO: Use a more precise rule than matching __name__ to decide + # whether two classes are the same. Also, save the formal + # parameters. (These things are related! A solution lies in + # using origin.) + + __extra__ = None + + def __new__(cls, name, bases, namespace, + parameters=None, origin=None, extra=None): + if parameters is None: + # Extract parameters from direct base classes. Only + # direct bases are considered and only those that are + # themselves generic, and parameterized with type + # variables. Don't use bases like Any, Union, Tuple, + # Callable or type variables. + params = None + for base in bases: + if isinstance(base, TypingMeta): + if not isinstance(base, GenericMeta): + raise TypeError( + "You cannot inherit from magic class %s" % + repr(base)) + if base.__parameters__ is None: + continue # The base is unparameterized. + for bp in base.__parameters__: + if _has_type_var(bp) and not isinstance(bp, TypeVar): + raise TypeError( + "Cannot inherit from a generic class " + "parameterized with " + "non-type-variable %s" % bp) + if params is None: + params = [] + if bp not in params: + params.append(bp) + if params is not None: + parameters = tuple(params) + self = super().__new__(cls, name, bases, namespace, _root=True) + self.__parameters__ = parameters + if extra is not None: + self.__extra__ = extra + # Else __extra__ is inherited, eventually from the + # (meta-)class default above. + self.__origin__ = origin + return self + + def _has_type_var(self): + if self.__parameters__: + for t in self.__parameters__: + if _has_type_var(t): + return True + return False + + def __repr__(self): + r = super().__repr__() + if self.__parameters__ is not None: + r += '[%s]' % ( + ', '.join(_type_repr(p) for p in self.__parameters__)) + return r + + def __eq__(self, other): + if not isinstance(other, GenericMeta): + return NotImplemented + return (_geqv(self, other) and + self.__parameters__ == other.__parameters__) + + def __hash__(self): + return hash((self.__name__, self.__parameters__)) + + def __getitem__(self, params): + if not isinstance(params, tuple): + params = (params,) + if not params: + raise TypeError("Cannot have empty parameter list") + msg = "Parameters to generic types must be types." + params = tuple(_type_check(p, msg) for p in params) + if self.__parameters__ is None: + for p in params: + if not isinstance(p, TypeVar): + raise TypeError("Initial parameters must be " + "type variables; got %s" % p) + else: + if len(params) != len(self.__parameters__): + raise TypeError("Cannot change parameter count from %d to %d" % + (len(self.__parameters__), len(params))) + for new, old in zip(params, self.__parameters__): + if isinstance(old, TypeVar): + if not old.__constraints__: + # Substituting for an unconstrained TypeVar is OK. + continue + if issubclass(new, Union[old.__constraints__]): + # Specializing a constrained type variable is OK. + continue + if not issubclass(new, old): + raise TypeError( + "Cannot substitute %s for %s in %s" % + (_type_repr(new), _type_repr(old), self)) + + return self.__class__(self.__name__, self.__bases__, + dict(self.__dict__), + parameters=params, + origin=self, + extra=self.__extra__) + + def __subclasscheck__(self, cls): + if cls is Any: + return True + if isinstance(cls, GenericMeta): + # For a class C(Generic[T]) where T is co-variant, + # 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.__parameters__) == len(origin.__parameters__) + assert len(cls.__parameters__) == len(origin.__parameters__) + for p_self, p_cls, p_origin in zip(self.__parameters__, + cls.__parameters__, + 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 super().__subclasscheck__(cls): + return True + if self.__extra__ is None or isinstance(cls, GenericMeta): + return False + return issubclass(cls, self.__extra__) + + def __instancecheck__(self, obj): + if super().__instancecheck__(obj): + return True + if self.__extra__ is None: + return False + return isinstance(obj, self.__extra__) + + +class Generic(metaclass=GenericMeta): + """Abstract base class for generic types. + + A generic type is typically declared by inheriting from an + instantiation of this class with one or more type variables. + For example, a generic mapping type might be defined as:: + + class Mapping(Generic[KT, VT]): + def __getitem__(self, key: KT) -> VT: + ... + # Etc. + + This class can then be used as follows:: + + def lookup_name(mapping: Mapping, key: KT, default: VT) -> VT: + try: + return mapping[key] + except KeyError: + return default + + For clarity the type variables may be redefined, e.g.:: + + X = TypeVar('X') + Y = TypeVar('Y') + def lookup_name(mapping: Mapping[X, Y], key: X, default: Y) -> Y: + # Same body as above. + """ + + def __new__(cls, *args, **kwds): + next_in_mro = object + # Look for the last occurrence of Generic or Generic[...]. + for i, c in enumerate(cls.__mro__[:-1]): + if isinstance(c, GenericMeta) and _gorg(c) is Generic: + next_in_mro = cls.__mro__[i+1] + return next_in_mro.__new__(_gorg(cls)) + + +def cast(typ, val): + """Cast a value to a type. + + This returns the value unchanged. To the type checker this + signals that the return value has the designated type, but at + runtime we intentionally don't check anything (we want this + to be as fast as possible). + """ + return val + + +def _get_defaults(func): + """Internal helper to extract the default arguments, by name.""" + code = func.__code__ + pos_count = code.co_argcount + kw_count = code.co_kwonlyargcount + arg_names = code.co_varnames + kwarg_names = arg_names[pos_count:pos_count + kw_count] + arg_names = arg_names[:pos_count] + defaults = func.__defaults__ or () + kwdefaults = func.__kwdefaults__ + res = dict(kwdefaults) if kwdefaults else {} + pos_offset = pos_count - len(defaults) + for name, value in zip(arg_names[pos_offset:], defaults): + assert name not in res + res[name] = value + return res + + +def get_type_hints(obj, globalns=None, localns=None): + """Return type hints for a function or method object. + + This is often the same as obj.__annotations__, but it handles + forward references encoded as string literals, and if necessary + adds Optional[t] if a default value equal to None is set. + + BEWARE -- the behavior of globalns and localns is counterintuitive + (unless you are familiar with how eval() and exec() work). The + search order is locals first, then globals. + + - If no dict arguments are passed, an attempt is made to use the + globals from obj, and these are also used as the locals. If the + object does not appear to have globals, an exception is raised. + + - If one dict argument is passed, it is used for both globals and + locals. + + - If two dict arguments are passed, they specify globals and + locals, respectively. + """ + if getattr(obj, '__no_type_check__', None): + return {} + if globalns is None: + globalns = getattr(obj, '__globals__', {}) + if localns is None: + localns = globalns + elif localns is None: + localns = globalns + defaults = _get_defaults(obj) + hints = dict(obj.__annotations__) + for name, value in hints.items(): + if isinstance(value, str): + value = _ForwardRef(value) + value = _eval_type(value, globalns, localns) + if name in defaults and defaults[name] is None: + value = Optional[value] + hints[name] = value + return hints + + +# TODO: Also support this as a class decorator. +def no_type_check(arg): + """Decorator to indicate that annotations are not type hints. + + The argument must be a class or function; if it is a class, it + applies recursively to all methods defined in that class (but not + to methods defined in its superclasses or subclasses). + + This mutates the function(s) in place. + """ + if isinstance(arg, type): + for obj in arg.__dict__.values(): + if isinstance(obj, types.FunctionType): + obj.__no_type_check__ = True + else: + arg.__no_type_check__ = True + return arg + + +def no_type_check_decorator(decorator): + """Decorator to give another decorator the @no_type_check effect. + + This wraps the decorator with something that wraps the decorated + function in @no_type_check. + """ + + @functools.wraps(decorator) + def wrapped_decorator(*args, **kwds): + func = decorator(*args, **kwds) + func = no_type_check(func) + return func + + return wrapped_decorator + + +def overload(func): + raise RuntimeError("Overloading is only supported in library stubs") + + +class _ProtocolMeta(GenericMeta): + """Internal metaclass for _Protocol. + + This exists so _Protocol classes can be generic without deriving + from Generic. + """ + + def __subclasscheck__(self, cls): + if not self._is_protocol: + # No structural checks since this isn't a protocol. + return NotImplemented + + if self is _Protocol: + # Every class is a subclass of the empty protocol. + return True + + # Find all attributes defined in the protocol. + attrs = self._get_protocol_attrs() + + for attr in attrs: + if not any(attr in d.__dict__ for d in cls.__mro__): + return False + return True + + def _get_protocol_attrs(self): + # Get all Protocol base classes. + protocol_bases = [] + for c in self.__mro__: + if getattr(c, '_is_protocol', False) and c.__name__ != '_Protocol': + protocol_bases.append(c) + + # Get attributes included in protocol. + attrs = set() + for base in protocol_bases: + for attr in base.__dict__.keys(): + # Include attributes not defined in any non-protocol bases. + for c in self.__mro__: + if (c is not base and attr in c.__dict__ and + not getattr(c, '_is_protocol', False)): + break + else: + if (not attr.startswith('_abc_') and + attr != '__abstractmethods__' and + attr != '_is_protocol' and + attr != '__dict__' and + attr != '_get_protocol_attrs' and + attr != '__parameters__' and + attr != '__origin__' and + attr != '__module__'): + attrs.add(attr) + + return attrs + + +class _Protocol(metaclass=_ProtocolMeta): + """Internal base class for protocol classes. + + This implements a simple-minded structural isinstance check + (similar but more general than the one-offs in collections.abc + such as Hashable). + """ + + _is_protocol = True + + +# Various ABCs mimicking those in collections.abc. +# A few are simply re-exported for completeness. + +Hashable = collections_abc.Hashable # Not generic. + + +class Iterable(Generic[T_co], extra=collections_abc.Iterable): + pass + + +class Iterator(Iterable[T_co], extra=collections_abc.Iterator): + pass + + +class SupportsInt(_Protocol): + + @abstractmethod + def __int__(self) -> int: + pass + + +class SupportsFloat(_Protocol): + + @abstractmethod + def __float__(self) -> float: + pass + + +class SupportsComplex(_Protocol): + + @abstractmethod + def __complex__(self) -> complex: + pass + + +class SupportsBytes(_Protocol): + + @abstractmethod + def __bytes__(self) -> bytes: + pass + + +class SupportsAbs(_Protocol[T]): + + @abstractmethod + def __abs__(self) -> T: + pass + + +class SupportsRound(_Protocol[T]): + + @abstractmethod + def __round__(self, ndigits: int = 0) -> T: + pass + + +class Reversible(_Protocol[T]): + + @abstractmethod + def __reversed__(self) -> 'Iterator[T]': + pass + + +Sized = collections_abc.Sized # Not generic. + + +class Container(Generic[T_co], extra=collections_abc.Container): + pass + + +# Callable was defined earlier. + + +class AbstractSet(Sized, Iterable[T_co], Container[T_co], + extra=collections_abc.Set): + pass + + +class MutableSet(AbstractSet[T], extra=collections_abc.MutableSet): + pass + + +class Mapping(Sized, Iterable[KT_co], Container[KT_co], Generic[KT_co, VT_co], + extra=collections_abc.Mapping): + pass + + +class MutableMapping(Mapping[KT, VT], extra=collections_abc.MutableMapping): + pass + + +class Sequence(Sized, Iterable[T_co], Container[T_co], + extra=collections_abc.Sequence): + pass + + +class MutableSequence(Sequence[T], extra=collections_abc.MutableSequence): + pass + + +class ByteString(Sequence[int], extra=collections_abc.ByteString): + pass + + +ByteString.register(type(memoryview(b''))) + + +class _ListMeta(GenericMeta): + + def __instancecheck__(self, obj): + if not super().__instancecheck__(obj): + return False + itemtype = self.__parameters__[0] + for x in obj: + if not isinstance(x, itemtype): + return False + return True + + +class List(list, MutableSequence[T], metaclass=_ListMeta): + + def __new__(cls, *args, **kwds): + if _geqv(cls, List): + raise TypeError("Type List cannot be instantiated; " + "use list() instead") + return list.__new__(cls, *args, **kwds) + + +class _SetMeta(GenericMeta): + + def __instancecheck__(self, obj): + if not super().__instancecheck__(obj): + return False + itemtype = self.__parameters__[0] + for x in obj: + if not isinstance(x, itemtype): + return False + return True + + +class Set(set, MutableSet[T], metaclass=_SetMeta): + + def __new__(cls, *args, **kwds): + if _geqv(cls, Set): + raise TypeError("Type Set cannot be instantiated; " + "use set() instead") + return set.__new__(cls, *args, **kwds) + + +class _FrozenSetMeta(_SetMeta): + """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) + + def __instancecheck__(self, obj): + if issubclass(obj.__class__, Set): + return False + return super().__instancecheck__(obj) + + +class FrozenSet(frozenset, AbstractSet[T_co], metaclass=_FrozenSetMeta): + + def __new__(cls, *args, **kwds): + if _geqv(cls, FrozenSet): + raise TypeError("Type FrozenSet cannot be instantiated; " + "use frozenset() instead") + return frozenset.__new__(cls, *args, **kwds) + + +class MappingView(Sized, Iterable[T_co], extra=collections_abc.MappingView): + pass + + +class KeysView(MappingView[KT_co], AbstractSet[KT_co], + extra=collections_abc.KeysView): + pass + + +# TODO: Enable Set[Tuple[KT_co, VT_co]] instead of Generic[KT_co, VT_co]. +class ItemsView(MappingView, Generic[KT_co, VT_co], + extra=collections_abc.ItemsView): + pass + + +class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView): + pass + + +class _DictMeta(GenericMeta): + + def __instancecheck__(self, obj): + if not super().__instancecheck__(obj): + return False + keytype, valuetype = self.__parameters__ + for key, value in obj.items(): + if not (isinstance(key, keytype) and + isinstance(value, valuetype)): + return False + return True + + +class Dict(dict, MutableMapping[KT, VT], metaclass=_DictMeta): + + def __new__(cls, *args, **kwds): + if _geqv(cls, Dict): + raise TypeError("Type Dict cannot be instantiated; " + "use dict() instead") + return dict.__new__(cls, *args, **kwds) + + +# Determine what base class to use for Generator. +if hasattr(collections_abc, 'Generator'): + # Sufficiently recent versions of 3.5 have a Generator ABC. + _G_base = collections_abc.Generator +else: + # Fall back on the exact type. + _G_base = types.GeneratorType + + +class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co], + extra=_G_base): + + def __new__(cls, *args, **kwds): + if _geqv(cls, Generator): + raise TypeError("Type Generator cannot be instantiated; " + "create a subclass instead") + return super().__new__(cls, *args, **kwds) + + +def NamedTuple(typename, fields): + """Typed version of namedtuple. + + Usage:: + + Employee = typing.NamedTuple('Employee', [('name', str), 'id', int)]) + + This is equivalent to:: + + Employee = collections.namedtuple('Employee', ['name', 'id']) + + The resulting class has one extra attribute: _field_types, + giving a dict mapping field names to types. (The field names + are in the _fields attribute, which is part of the namedtuple + API.) + """ + fields = [(n, t) for n, t in fields] + cls = collections.namedtuple(typename, [n for n, t in fields]) + cls._field_types = dict(fields) + return cls + + +class IO(Generic[AnyStr]): + """Generic base class for TextIO and BinaryIO. + + This is an abstract, generic version of the return of open(). + + NOTE: This does not distinguish between the different possible + classes (text vs. binary, read vs. write vs. read/write, + append-only, unbuffered). The TextIO and BinaryIO subclasses + below capture the distinctions between text vs. binary, which is + pervasive in the interface; however we currently do not offer a + way to track the other distinctions in the type system. + """ + + @abstractproperty + def mode(self) -> str: + pass + + @abstractproperty + def name(self) -> str: + pass + + @abstractmethod + def close(self) -> None: + pass + + @abstractmethod + def closed(self) -> bool: + pass + + @abstractmethod + def fileno(self) -> int: + pass + + @abstractmethod + def flush(self) -> None: + pass + + @abstractmethod + def isatty(self) -> bool: + pass + + @abstractmethod + def read(self, n: int = -1) -> AnyStr: + pass + + @abstractmethod + def readable(self) -> bool: + pass + + @abstractmethod + def readline(self, limit: int = -1) -> AnyStr: + pass + + @abstractmethod + def readlines(self, hint: int = -1) -> List[AnyStr]: + pass + + @abstractmethod + def seek(self, offset: int, whence: int = 0) -> int: + pass + + @abstractmethod + def seekable(self) -> bool: + pass + + @abstractmethod + def tell(self) -> int: + pass + + @abstractmethod + def truncate(self, size: int = None) -> int: + pass + + @abstractmethod + def writable(self) -> bool: + pass + + @abstractmethod + def write(self, s: AnyStr) -> int: + pass + + @abstractmethod + def writelines(self, lines: List[AnyStr]) -> None: + pass + + @abstractmethod + def __enter__(self) -> 'IO[AnyStr]': + pass + + @abstractmethod + def __exit__(self, type, value, traceback) -> None: + pass + + +class BinaryIO(IO[bytes]): + """Typed version of the return of open() in binary mode.""" + + @abstractmethod + def write(self, s: Union[bytes, bytearray]) -> int: + pass + + @abstractmethod + def __enter__(self) -> 'BinaryIO': + pass + + +class TextIO(IO[str]): + """Typed version of the return of open() in text mode.""" + + @abstractproperty + def buffer(self) -> BinaryIO: + pass + + @abstractproperty + def encoding(self) -> str: + pass + + @abstractproperty + def errors(self) -> str: + pass + + @abstractproperty + def line_buffering(self) -> bool: + pass + + @abstractproperty + def newlines(self) -> Any: + pass + + @abstractmethod + def __enter__(self) -> 'TextIO': + pass + + +class io: + """Wrapper namespace for IO generic classes.""" + + __all__ = ['IO', 'TextIO', 'BinaryIO'] + IO = IO + TextIO = TextIO + BinaryIO = BinaryIO + +io.__name__ = __name__ + '.io' +sys.modules[io.__name__] = io + + +Pattern = _TypeAlias('Pattern', AnyStr, type(stdlib_re.compile('')), + lambda p: p.pattern) +Match = _TypeAlias('Match', AnyStr, type(stdlib_re.match('', '')), + lambda m: m.re.pattern) + + +class re: + """Wrapper namespace for re type aliases.""" + + __all__ = ['Pattern', 'Match'] + Pattern = Pattern + Match = Match + +re.__name__ = __name__ + '.re' +sys.modules[re.__name__] = re diff --git a/Misc/NEWS b/Misc/NEWS index 9c858dbaf1a..8a1c77943f1 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -58,6 +58,8 @@ Core and Builtins Library ------- +- Issue #23973: PEP 484: Add the typing module. + - Issue #20035: Replaced the ``tkinter._fix`` module used for setting up the Tcl/Tk environment on Windows with a private function in the ``_tkinter`` module that makes no permanent changes to the environment.