From bd5b9a074241c6146bdcacef9a1e84f48c7c8d8a Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 5 Apr 2016 08:28:52 -0700 Subject: [PATCH] Many changes from the upstream repo (https://github.com/python/typing). This syncs to rev 7b43ada77821d23e55e3a4b35f6055a59b9e1ad7 there. Summary: - Add typing.DefaultDict (as a generic variant of collections.defaultdict). - Use collections.Reversible if it exists (only relevant for Python 3.6). - Revamped generic class behavior to conform to updated PEP 484. - Improve speed of Generic.__new__. - Make sure __init__ is called for new Generic instances. Fix issue #26391. - Refactor async support to be compatible with 3.2, 3.3, 3.4. - Remove 'io' and 're' from __all__ (they still exist, just not included by "import *"). Fix issue #26234. - Change @overload -- you can now use it outside stubs (you still cannot call the decorated function though). --- Lib/test/test_typing.py | 156 +++++++++++++--- Lib/typing.py | 396 ++++++++++++++++++++++++---------------- 2 files changed, 372 insertions(+), 180 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index b9ca64259f3..f1c6e12b216 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1,8 +1,7 @@ -import asyncio import pickle import re import sys -from unittest import TestCase, main +from unittest import TestCase, main, skipUnless from typing import Any from typing import TypeVar, AnyStr @@ -133,6 +132,7 @@ class TypeVarTests(TestCase): def test_constrained_error(self): with self.assertRaises(TypeError): X = TypeVar('X', int) + X def test_union_unique(self): X = TypeVar('X') @@ -317,6 +317,7 @@ class UnionTests(TestCase): def test_union_str_pattern(self): # Shouldn't crash; see http://bugs.python.org/issue25390 A = Union[str, Pattern] + A class TypeVarUnionTests(TestCase): @@ -487,7 +488,7 @@ class SimpleMapping(Generic[XK, XV]): ... -class MySimpleMapping(SimpleMapping): +class MySimpleMapping(SimpleMapping[XK, XV]): def __init__(self): self.store = {} @@ -541,6 +542,7 @@ class ProtocolTests(TestCase): assert not issubclass(str, typing.SupportsAbs) def test_supports_round(self): + issubclass(float, typing.SupportsRound) assert issubclass(float, typing.SupportsRound) assert issubclass(int, typing.SupportsRound) assert not issubclass(str, typing.SupportsRound) @@ -551,20 +553,23 @@ class ProtocolTests(TestCase): def test_protocol_instance_type_error(self): with self.assertRaises(TypeError): - isinstance([], typing.Reversible) + isinstance(0, typing.SupportsAbs) class GenericTests(TestCase): def test_basics(self): X = SimpleMapping[str, Any] + assert X.__parameters__ == () + with self.assertRaises(TypeError): + X[str] + with self.assertRaises(TypeError): + X[str, str] Y = SimpleMapping[XK, str] - X[str, str] - Y[str, str] + assert Y.__parameters__ == (XK,) + Y[str] with self.assertRaises(TypeError): - X[int, str] - with self.assertRaises(TypeError): - Y[str, bytes] + Y[str, str] def test_init(self): T = TypeVar('T') @@ -576,30 +581,61 @@ class GenericTests(TestCase): def test_repr(self): self.assertEqual(repr(SimpleMapping), - __name__ + '.' + 'SimpleMapping[~XK, ~XV]') + __name__ + '.' + 'SimpleMapping<~XK, ~XV>') self.assertEqual(repr(MySimpleMapping), - __name__ + '.' + 'MySimpleMapping[~XK, ~XV]') + __name__ + '.' + 'MySimpleMapping<~XK, ~XV>') + + def test_chain_repr(self): + T = TypeVar('T') + S = TypeVar('S') + + class C(Generic[T]): + pass + + X = C[Tuple[S, T]] + assert X == C[Tuple[S, T]] + assert X != C[Tuple[T, S]] + + Y = X[T, int] + assert Y == X[T, int] + assert Y != X[S, int] + assert Y != X[T, str] + + Z = Y[str] + assert Z == Y[str] + assert Z != Y[int] + assert Z != Y[T] + + assert str(Z).endswith( + '.C<~T>[typing.Tuple[~S, ~T]]<~S, ~T>[~T, int]<~T>[str]') def test_dict(self): T = TypeVar('T') + class B(Generic[T]): pass + b = B() b.foo = 42 self.assertEqual(b.__dict__, {'foo': 42}) + class C(B[int]): pass + c = C() c.bar = 'abc' self.assertEqual(c.__dict__, {'bar': 'abc'}) def test_pickle(self): + global C # pickle wants to reference the class by name T = TypeVar('T') + class B(Generic[T]): pass - global C # pickle wants to reference the class by name + class C(B[int]): pass + c = C() c.foo = 42 c.bar = 'abc' @@ -626,12 +662,12 @@ class GenericTests(TestCase): assert C.__module__ == __name__ if not PY32: assert C.__qualname__ == 'GenericTests.test_repr_2..C' - assert repr(C).split('.')[-1] == 'C[~T]' + 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]' + assert repr(X).split('.')[-1] == 'C<~T>[int]' class Y(C[int]): pass @@ -639,7 +675,7 @@ class GenericTests(TestCase): assert Y.__module__ == __name__ if not PY32: assert Y.__qualname__ == 'GenericTests.test_repr_2..Y' - assert repr(Y).split('.')[-1] == 'Y[int]' + assert repr(Y).split('.')[-1] == 'Y' def test_eq_1(self): assert Generic == Generic @@ -667,15 +703,14 @@ class GenericTests(TestCase): class B(Generic[KT, T]): pass - class C(A, Generic[KT, VT], B): + class C(A[T, VT], Generic[VT, T, KT], B[KT, T]): pass - assert C.__parameters__ == (T, VT, KT) + assert C.__parameters__ == (VT, T, KT) def test_nested(self): - class G(Generic): - pass + G = Generic class Visitor(G[T]): @@ -721,9 +756,30 @@ class GenericTests(TestCase): assert type(a) is Node assert type(b) is Node assert type(c) is Node + assert a.label == x + assert b.label == x + assert c.label == x foo(42) + def test_implicit_any(self): + T = TypeVar('T') + + class C(Generic[T]): + pass + + class D(C): + pass + + assert D.__parameters__ == () + + with self.assertRaises(Exception): + D[int] + with self.assertRaises(Exception): + D[Any] + with self.assertRaises(Exception): + D[T] + class VarianceTests(TestCase): @@ -956,14 +1012,33 @@ class OverloadTests(TestCase): from typing import overload with self.assertRaises(RuntimeError): + @overload def blah(): pass + blah() + + def test_overload_succeeds(self): + from typing import overload + + @overload + def blah(): + pass + + def blah(): + pass + + blah() + + +PY35 = sys.version_info[:2] >= (3, 5) + +PY35_TESTS = """ +import asyncio T_a = TypeVar('T') - class AwaitableWrapper(typing.Awaitable[T_a]): def __init__(self, value): @@ -973,7 +1048,6 @@ class AwaitableWrapper(typing.Awaitable[T_a]): yield return self.value - class AsyncIteratorWrapper(typing.AsyncIterator[T_a]): def __init__(self, value: typing.Iterable[T_a]): @@ -989,6 +1063,10 @@ class AsyncIteratorWrapper(typing.AsyncIterator[T_a]): return data else: raise StopAsyncIteration +""" + +if PY35: + exec(PY35_TESTS) class CollectionsAbcTests(TestCase): @@ -1015,9 +1093,14 @@ class CollectionsAbcTests(TestCase): assert isinstance(it, typing.Iterator[int]) assert not isinstance(42, typing.Iterator) + @skipUnless(PY35, 'Python 3.5 required') def test_awaitable(self): - async def foo() -> typing.Awaitable[int]: - return await AwaitableWrapper(42) + ns = {} + exec( + "async def foo() -> typing.Awaitable[int]:\n" + " return await AwaitableWrapper(42)\n", + globals(), ns) + foo = ns['foo'] g = foo() assert issubclass(type(g), typing.Awaitable[int]) assert isinstance(g, typing.Awaitable) @@ -1028,6 +1111,7 @@ class CollectionsAbcTests(TestCase): typing.Awaitable[Manager]) g.send(None) # Run foo() till completion, to avoid warning. + @skipUnless(PY35, 'Python 3.5 required') def test_async_iterable(self): base_it = range(10) # type: Iterator[int] it = AsyncIteratorWrapper(base_it) @@ -1037,6 +1121,7 @@ class CollectionsAbcTests(TestCase): typing.AsyncIterable[Employee]) assert not isinstance(42, typing.AsyncIterable) + @skipUnless(PY35, 'Python 3.5 required') def test_async_iterator(self): base_it = range(10) # type: Iterator[int] it = AsyncIteratorWrapper(base_it) @@ -1127,6 +1212,22 @@ class CollectionsAbcTests(TestCase): d = MyDict() assert isinstance(d, MyDict) + def test_no_defaultdict_instantiation(self): + with self.assertRaises(TypeError): + typing.DefaultDict() + with self.assertRaises(TypeError): + typing.DefaultDict[KT, VT]() + with self.assertRaises(TypeError): + typing.DefaultDict[str, int]() + + def test_defaultdict_subclass_instantiation(self): + + class MyDefDict(typing.DefaultDict[str, int]): + pass + + dd = MyDefDict() + assert isinstance(dd, MyDefDict) + def test_no_set_instantiation(self): with self.assertRaises(TypeError): typing.Set() @@ -1251,7 +1352,7 @@ class IOTests(TestCase): return a.readline() a = stuff.__annotations__['a'] - assert a.__parameters__ == (str,) + assert a.__parameters__ == () def test_binaryio(self): @@ -1259,7 +1360,7 @@ class IOTests(TestCase): return a.readline() a = stuff.__annotations__['a'] - assert a.__parameters__ == (bytes,) + assert a.__parameters__ == () def test_io_submodule(self): from typing.io import IO, TextIO, BinaryIO, __all__, __name__ @@ -1346,8 +1447,9 @@ class AllTests(TestCase): assert 'ValuesView' in a assert 'cast' in a assert 'overload' in a - assert 'io' in a - assert 're' in a + # Check that io and re are not exported. + assert 'io' not in a + assert 're' not in a # Spot-check that stdlib modules aren't exported. assert 'os' not in a assert 'sys' not in a diff --git a/Lib/typing.py b/Lib/typing.py index 823f9be5d8b..de2a462e759 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1,7 +1,3 @@ -# 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 @@ -56,6 +52,7 @@ __all__ = [ # Concrete collection types. 'Dict', + 'DefaultDict', 'List', 'Set', 'NamedTuple', # Not really a type. @@ -68,12 +65,12 @@ __all__ = [ 'no_type_check', 'no_type_check_decorator', 'overload', - - # Submodules. - 'io', - 're', ] +# The pseudo-submodules 're' and 'io' are part of the public +# namespace, but excluded from __all__ because they might stomp on +# legitimate imports of those modules. + def _qualname(x): if sys.version_info[:2] >= (3, 3): @@ -117,8 +114,8 @@ class TypingMeta(type): """ return self - def _has_type_var(self): - return False + def _get_type_vars(self, tvars): + pass def __repr__(self): return '%s.%s' % (self.__module__, _qualname(self)) @@ -214,8 +211,8 @@ class _TypeAlias: 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)): + isinstance(args[0], str) and + isinstance(args[1], tuple)): # Close enough. raise TypeError("A type alias cannot be subclassed") return object.__new__(cls) @@ -271,8 +268,16 @@ class _TypeAlias: 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 _get_type_vars(types, tvars): + for t in types: + if isinstance(t, TypingMeta): + t._get_type_vars(tvars) + + +def _type_vars(types): + tvars = [] + _get_type_vars(types, tvars) + return tuple(tvars) def _eval_type(t, globalns, localns): @@ -376,7 +381,7 @@ class TypeVar(TypingMeta, metaclass=TypingMeta, _root=True): 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. + false. (TODO: Why is this needed? This may change. See #136.) Type variables may be marked covariant or contravariant by passing covariant=True or contravariant=True. See PEP 484 for more @@ -410,8 +415,9 @@ class TypeVar(TypingMeta, metaclass=TypingMeta, _root=True): self.__bound__ = None return self - def _has_type_var(self): - return True + def _get_type_vars(self, tvars): + if self not in tvars: + tvars.append(self) def __repr__(self): if self.__covariant__: @@ -448,7 +454,6 @@ 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) @@ -514,12 +519,9 @@ class UnionMeta(TypingMeta): return self.__class__(self.__name__, self.__bases__, {}, p, _root=True) - def _has_type_var(self): + def _get_type_vars(self, tvars): if self.__union_params__: - for t in self.__union_params__: - if _has_type_var(t): - return True - return False + _get_type_vars(self.__union_params__, tvars) def __repr__(self): r = super().__repr__() @@ -656,12 +658,9 @@ class TupleMeta(TypingMeta): self.__tuple_use_ellipsis__ = use_ellipsis return self - def _has_type_var(self): + def _get_type_vars(self, tvars): if self.__tuple_params__: - for t in self.__tuple_params__: - if _has_type_var(t): - return True - return False + _get_type_vars(self.__tuple_params__, tvars) def _eval_type(self, globalns, localns): tp = self.__tuple_params__ @@ -769,12 +768,9 @@ class CallableMeta(TypingMeta): self.__result__ = result return self - def _has_type_var(self): + def _get_type_vars(self, tvars): if self.__args__: - for t in self.__args__: - if _has_type_var(t): - return True - return _has_type_var(self.__result__) + _get_type_vars(self.__args__, tvars) def _eval_type(self, globalns, localns): if self.__args__ is None and self.__result__ is None: @@ -878,76 +874,106 @@ def _geqv(a, b): return _gorg(a) is _gorg(b) +def _next_in_mro(cls): + """Helper for Generic.__new__. + + Returns the class after the last occurrence of Generic or + Generic[...] in cls.__mro__. + """ + 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 + + 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) + tvars=None, args=None, origin=None, extra=None): self = super().__new__(cls, name, bases, namespace, _root=True) - self.__parameters__ = parameters + + if tvars is not None: + # Called from __getitem__() below. + assert origin is not None + assert all(isinstance(t, TypeVar) for t in tvars), tvars + else: + # Called from class statement. + assert tvars is None, tvars + assert args is None, args + assert origin is None, origin + + # Get the full set of tvars from the bases. + tvars = _type_vars(bases) + # Look for Generic[T1, ..., Tn]. + # If found, tvars must be a subset of it. + # If not found, tvars is it. + # Also check for and reject plain Generic, + # and reject multiple Generic[...]. + gvars = None + for base in bases: + if base is Generic: + raise TypeError("Cannot inherit from plain Generic") + if (isinstance(base, GenericMeta) and + base.__origin__ is Generic): + if gvars is not None: + raise TypeError( + "Cannot inherit from Generic[...] multiple types.") + gvars = base.__parameters__ + if gvars is None: + gvars = tvars + else: + tvarset = set(tvars) + gvarset = set(gvars) + if not tvarset <= gvarset: + raise TypeError( + "Some type variables (%s) " + "are not listed in Generic[%s]" % + (", ".join(str(t) for t in tvars if t not in gvarset), + ", ".join(str(g) for g in gvars))) + tvars = gvars + + self.__parameters__ = tvars + self.__args__ = args + self.__origin__ = origin if extra is not None: self.__extra__ = extra # Else __extra__ is inherited, eventually from the # (meta-)class default above. - self.__origin__ = origin + # Speed hack (https://github.com/python/typing/issues/196). + self.__next_in_mro__ = _next_in_mro(self) 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 _get_type_vars(self, tvars): + if self.__origin__ and self.__parameters__: + _get_type_vars(self.__parameters__, tvars) def __repr__(self): - r = super().__repr__() - if self.__parameters__ is not None: + if self.__origin__ is not None: + r = repr(self.__origin__) + else: + r = super().__repr__() + if self.__args__: r += '[%s]' % ( + ', '.join(_type_repr(p) for p in self.__args__)) + if self.__parameters__: + 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__) + if self.__origin__ is not None: + return (self.__origin__ is other.__origin__ and + self.__args__ == other.__args__ and + self.__parameters__ == other.__parameters__) + else: + return self is other def __hash__(self): return hash((self.__name__, self.__parameters__)) @@ -956,37 +982,45 @@ class GenericMeta(TypingMeta, abc.ABCMeta): if not isinstance(params, tuple): params = (params,) if not params: - raise TypeError("Cannot have empty parameter list") + raise TypeError( + "Parameter list to %s[...] cannot be empty" % _qualname(self)) 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) + if self is Generic: + # Generic can only be subscripted with unique type variables. + if not all(isinstance(p, TypeVar) for p in params): + raise TypeError( + "Parameters to Generic[...] must all be type variables") if len(set(params)) != len(params): raise TypeError( - "All type variables in Generic[...] must be distinct.") + "Parameters to Generic[...] must all be unique") + tvars = params + args = None + elif self is _Protocol: + # _Protocol is internal, don't check anything. + tvars = params + args = None + elif self.__origin__ in (Generic, _Protocol): + # Can't subscript Generic[...] or _Protocol[...]. + raise TypeError("Cannot subscript already-subscripted %s" % + repr(self)) 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,) + self.__bases__, + # Subscripting a regular Generic subclass. + if not self.__parameters__: + raise TypeError("%s is not a generic class" % repr(self)) + alen = len(params) + elen = len(self.__parameters__) + if alen != elen: + raise TypeError( + "Too %s parameters for %s; actual %s, expected %s" % + ("many" if alen > elen else "few", repr(self), alen, elen)) + tvars = _type_vars(params) + args = params + return self.__class__(self.__name__, + (self,) + self.__bases__, dict(self.__dict__), - parameters=params, + tvars=tvars, + args=args, origin=self, extra=self.__extra__) @@ -1006,10 +1040,10 @@ class GenericMeta(TypingMeta, abc.ABCMeta): # 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__, + 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__: @@ -1039,6 +1073,10 @@ class GenericMeta(TypingMeta, abc.ABCMeta): return issubclass(cls, self.__extra__) +# Prevent checks for Generic to crash when defining Generic. +Generic = None + + class Generic(metaclass=GenericMeta): """Abstract base class for generic types. @@ -1053,29 +1091,23 @@ class Generic(metaclass=GenericMeta): This class can then be used as follows:: - def lookup_name(mapping: Mapping, key: KT, default: VT) -> VT: + def lookup_name(mapping: Mapping[KT, VT], 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. """ __slots__ = () 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)) + if cls.__origin__ is None: + return cls.__next_in_mro__.__new__(cls) + else: + origin = _gorg(cls) + obj = cls.__next_in_mro__.__new__(origin) + obj.__init__(*args, **kwds) + return obj def cast(typ, val): @@ -1093,9 +1125,7 @@ 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__ @@ -1148,7 +1178,6 @@ def get_type_hints(obj, globalns=None, localns=None): return hints -# TODO: Also support this as a class decorator. def no_type_check(arg): """Decorator to indicate that annotations are not type hints. @@ -1183,8 +1212,42 @@ def no_type_check_decorator(decorator): return wrapped_decorator +def _overload_dummy(*args, **kwds): + """Helper for @overload to raise when called.""" + raise NotImplementedError( + "You should not call an overloaded function. " + "A series of @overload-decorated functions " + "outside a stub module should always be followed " + "by an implementation that is not @overload-ed.") + + def overload(func): - raise RuntimeError("Overloading is only supported in library stubs") + """Decorator for overloaded functions/methods. + + In a stub file, place two or more stub definitions for the same + function in a row, each decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + + In a non-stub file (i.e. a regular .py file), do the same but + follow it with an implementation. The implementation should *not* + be decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + def utf8(value): + # implementation goes here + """ + return _overload_dummy class _ProtocolMeta(GenericMeta): @@ -1232,14 +1295,16 @@ class _ProtocolMeta(GenericMeta): break else: if (not attr.startswith('_abc_') and - attr != '__abstractmethods__' and - attr != '_is_protocol' and - attr != '__dict__' and - attr != '__slots__' and - attr != '_get_protocol_attrs' and - attr != '__parameters__' and - attr != '__origin__' and - attr != '__module__'): + attr != '__abstractmethods__' and + attr != '_is_protocol' and + attr != '__dict__' and + attr != '__args__' and + attr != '__slots__' and + attr != '_get_protocol_attrs' and + attr != '__next_in_mro__' and + attr != '__parameters__' and + attr != '__origin__' and + attr != '__module__'): attrs.add(attr) return attrs @@ -1264,16 +1329,25 @@ class _Protocol(metaclass=_ProtocolMeta): Hashable = collections_abc.Hashable # Not generic. -class Awaitable(Generic[T_co], extra=collections_abc.Awaitable): - __slots__ = () +if hasattr(collections_abc, 'Awaitable'): + class Awaitable(Generic[T_co], extra=collections_abc.Awaitable): + __slots__ = () +else: + Awaitable = None -class AsyncIterable(Generic[T_co], extra=collections_abc.AsyncIterable): - __slots__ = () +if hasattr(collections_abc, 'AsyncIterable'): + class AsyncIterable(Generic[T_co], extra=collections_abc.AsyncIterable): + __slots__ = () -class AsyncIterator(AsyncIterable[T_co], extra=collections_abc.AsyncIterator): - __slots__ = () + class AsyncIterator(AsyncIterable[T_co], + extra=collections_abc.AsyncIterator): + __slots__ = () + +else: + AsyncIterable = None + AsyncIterator = None class Iterable(Generic[T_co], extra=collections_abc.Iterable): @@ -1332,12 +1406,16 @@ class SupportsRound(_Protocol[T_co]): pass -class Reversible(_Protocol[T_co]): - __slots__ = () +if hasattr(collections_abc, 'Reversible'): + class Reversible(Iterable[T_co], extra=collections_abc.Reversible): + __slots__ = () +else: + class Reversible(_Protocol[T_co]): + __slots__ = () - @abstractmethod - def __reversed__(self) -> 'Iterator[T_co]': - pass + @abstractmethod + def __reversed__(self) -> 'Iterator[T_co]': + pass Sized = collections_abc.Sized # Not generic. @@ -1360,7 +1438,7 @@ class MutableSet(AbstractSet[T], extra=collections_abc.MutableSet): # NOTE: Only the value type is covariant. -class Mapping(Sized, Iterable[KT], Container[KT], Generic[VT_co], +class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co], extra=collections_abc.Mapping): pass @@ -1368,10 +1446,14 @@ class Mapping(Sized, Iterable[KT], Container[KT], Generic[VT_co], class MutableMapping(Mapping[KT, VT], extra=collections_abc.MutableMapping): pass - -class Sequence(Sized, Iterable[T_co], Container[T_co], +if hasattr(collections_abc, 'Reversible'): + class Sequence(Sized, Reversible[T_co], Container[T_co], extra=collections_abc.Sequence): - pass + pass +else: + class Sequence(Sized, Iterable[T_co], Container[T_co], + extra=collections_abc.Sequence): + pass class MutableSequence(Sequence[T], extra=collections_abc.MutableSequence): @@ -1436,8 +1518,9 @@ class KeysView(MappingView[KT], AbstractSet[KT], pass -# TODO: Enable Set[Tuple[KT, VT_co]] instead of Generic[KT, VT_co]. -class ItemsView(MappingView, Generic[KT, VT_co], +class ItemsView(MappingView[Tuple[KT, VT_co]], + Set[Tuple[KT, VT_co]], + Generic[KT, VT_co], extra=collections_abc.ItemsView): pass @@ -1454,6 +1537,13 @@ class Dict(dict, MutableMapping[KT, VT]): "use dict() instead") return dict.__new__(cls, *args, **kwds) +class DefaultDict(collections.defaultdict, MutableMapping[KT, VT]): + + def __new__(cls, *args, **kwds): + if _geqv(cls, DefaultDict): + raise TypeError("Type DefaultDict cannot be instantiated; " + "use collections.defaultdict() instead") + return collections.defaultdict.__new__(cls, *args, **kwds) # Determine what base class to use for Generator. if hasattr(collections_abc, 'Generator'):