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).
This commit is contained in:
Guido van Rossum 2016-04-05 08:28:52 -07:00
parent 0f7673943a
commit bd5b9a0742
2 changed files with 372 additions and 180 deletions

View File

@ -1,8 +1,7 @@
import asyncio
import pickle import pickle
import re import re
import sys import sys
from unittest import TestCase, main from unittest import TestCase, main, skipUnless
from typing import Any from typing import Any
from typing import TypeVar, AnyStr from typing import TypeVar, AnyStr
@ -133,6 +132,7 @@ class TypeVarTests(TestCase):
def test_constrained_error(self): def test_constrained_error(self):
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
X = TypeVar('X', int) X = TypeVar('X', int)
X
def test_union_unique(self): def test_union_unique(self):
X = TypeVar('X') X = TypeVar('X')
@ -317,6 +317,7 @@ class UnionTests(TestCase):
def test_union_str_pattern(self): def test_union_str_pattern(self):
# Shouldn't crash; see http://bugs.python.org/issue25390 # Shouldn't crash; see http://bugs.python.org/issue25390
A = Union[str, Pattern] A = Union[str, Pattern]
A
class TypeVarUnionTests(TestCase): class TypeVarUnionTests(TestCase):
@ -487,7 +488,7 @@ class SimpleMapping(Generic[XK, XV]):
... ...
class MySimpleMapping(SimpleMapping): class MySimpleMapping(SimpleMapping[XK, XV]):
def __init__(self): def __init__(self):
self.store = {} self.store = {}
@ -541,6 +542,7 @@ class ProtocolTests(TestCase):
assert not issubclass(str, typing.SupportsAbs) assert not issubclass(str, typing.SupportsAbs)
def test_supports_round(self): def test_supports_round(self):
issubclass(float, typing.SupportsRound)
assert issubclass(float, typing.SupportsRound) assert issubclass(float, typing.SupportsRound)
assert issubclass(int, typing.SupportsRound) assert issubclass(int, typing.SupportsRound)
assert not issubclass(str, typing.SupportsRound) assert not issubclass(str, typing.SupportsRound)
@ -551,20 +553,23 @@ class ProtocolTests(TestCase):
def test_protocol_instance_type_error(self): def test_protocol_instance_type_error(self):
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
isinstance([], typing.Reversible) isinstance(0, typing.SupportsAbs)
class GenericTests(TestCase): class GenericTests(TestCase):
def test_basics(self): def test_basics(self):
X = SimpleMapping[str, Any] X = SimpleMapping[str, Any]
assert X.__parameters__ == ()
with self.assertRaises(TypeError):
X[str]
with self.assertRaises(TypeError):
X[str, str]
Y = SimpleMapping[XK, str] Y = SimpleMapping[XK, str]
X[str, str] assert Y.__parameters__ == (XK,)
Y[str, str] Y[str]
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
X[int, str] Y[str, str]
with self.assertRaises(TypeError):
Y[str, bytes]
def test_init(self): def test_init(self):
T = TypeVar('T') T = TypeVar('T')
@ -576,30 +581,61 @@ class GenericTests(TestCase):
def test_repr(self): def test_repr(self):
self.assertEqual(repr(SimpleMapping), self.assertEqual(repr(SimpleMapping),
__name__ + '.' + 'SimpleMapping[~XK, ~XV]') __name__ + '.' + 'SimpleMapping<~XK, ~XV>')
self.assertEqual(repr(MySimpleMapping), 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): def test_dict(self):
T = TypeVar('T') T = TypeVar('T')
class B(Generic[T]): class B(Generic[T]):
pass pass
b = B() b = B()
b.foo = 42 b.foo = 42
self.assertEqual(b.__dict__, {'foo': 42}) self.assertEqual(b.__dict__, {'foo': 42})
class C(B[int]): class C(B[int]):
pass pass
c = C() c = C()
c.bar = 'abc' c.bar = 'abc'
self.assertEqual(c.__dict__, {'bar': 'abc'}) self.assertEqual(c.__dict__, {'bar': 'abc'})
def test_pickle(self): def test_pickle(self):
global C # pickle wants to reference the class by name
T = TypeVar('T') T = TypeVar('T')
class B(Generic[T]): class B(Generic[T]):
pass pass
global C # pickle wants to reference the class by name
class C(B[int]): class C(B[int]):
pass pass
c = C() c = C()
c.foo = 42 c.foo = 42
c.bar = 'abc' c.bar = 'abc'
@ -626,12 +662,12 @@ class GenericTests(TestCase):
assert C.__module__ == __name__ assert C.__module__ == __name__
if not PY32: if not PY32:
assert C.__qualname__ == 'GenericTests.test_repr_2.<locals>.C' assert C.__qualname__ == 'GenericTests.test_repr_2.<locals>.C'
assert repr(C).split('.')[-1] == 'C[~T]' assert repr(C).split('.')[-1] == 'C<~T>'
X = C[int] X = C[int]
assert X.__module__ == __name__ assert X.__module__ == __name__
if not PY32: if not PY32:
assert X.__qualname__ == 'C' assert X.__qualname__ == 'C'
assert repr(X).split('.')[-1] == 'C[int]' assert repr(X).split('.')[-1] == 'C<~T>[int]'
class Y(C[int]): class Y(C[int]):
pass pass
@ -639,7 +675,7 @@ class GenericTests(TestCase):
assert Y.__module__ == __name__ assert Y.__module__ == __name__
if not PY32: if not PY32:
assert Y.__qualname__ == 'GenericTests.test_repr_2.<locals>.Y' assert Y.__qualname__ == 'GenericTests.test_repr_2.<locals>.Y'
assert repr(Y).split('.')[-1] == 'Y[int]' assert repr(Y).split('.')[-1] == 'Y'
def test_eq_1(self): def test_eq_1(self):
assert Generic == Generic assert Generic == Generic
@ -667,15 +703,14 @@ class GenericTests(TestCase):
class B(Generic[KT, T]): class B(Generic[KT, T]):
pass pass
class C(A, Generic[KT, VT], B): class C(A[T, VT], Generic[VT, T, KT], B[KT, T]):
pass pass
assert C.__parameters__ == (T, VT, KT) assert C.__parameters__ == (VT, T, KT)
def test_nested(self): def test_nested(self):
class G(Generic): G = Generic
pass
class Visitor(G[T]): class Visitor(G[T]):
@ -721,9 +756,30 @@ class GenericTests(TestCase):
assert type(a) is Node assert type(a) is Node
assert type(b) is Node assert type(b) is Node
assert type(c) is Node assert type(c) is Node
assert a.label == x
assert b.label == x
assert c.label == x
foo(42) 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): class VarianceTests(TestCase):
@ -956,14 +1012,33 @@ class OverloadTests(TestCase):
from typing import overload from typing import overload
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
@overload @overload
def blah(): def blah():
pass 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') T_a = TypeVar('T')
class AwaitableWrapper(typing.Awaitable[T_a]): class AwaitableWrapper(typing.Awaitable[T_a]):
def __init__(self, value): def __init__(self, value):
@ -973,7 +1048,6 @@ class AwaitableWrapper(typing.Awaitable[T_a]):
yield yield
return self.value return self.value
class AsyncIteratorWrapper(typing.AsyncIterator[T_a]): class AsyncIteratorWrapper(typing.AsyncIterator[T_a]):
def __init__(self, value: typing.Iterable[T_a]): def __init__(self, value: typing.Iterable[T_a]):
@ -989,6 +1063,10 @@ class AsyncIteratorWrapper(typing.AsyncIterator[T_a]):
return data return data
else: else:
raise StopAsyncIteration raise StopAsyncIteration
"""
if PY35:
exec(PY35_TESTS)
class CollectionsAbcTests(TestCase): class CollectionsAbcTests(TestCase):
@ -1015,9 +1093,14 @@ class CollectionsAbcTests(TestCase):
assert isinstance(it, typing.Iterator[int]) assert isinstance(it, typing.Iterator[int])
assert not isinstance(42, typing.Iterator) assert not isinstance(42, typing.Iterator)
@skipUnless(PY35, 'Python 3.5 required')
def test_awaitable(self): def test_awaitable(self):
async def foo() -> typing.Awaitable[int]: ns = {}
return await AwaitableWrapper(42) exec(
"async def foo() -> typing.Awaitable[int]:\n"
" return await AwaitableWrapper(42)\n",
globals(), ns)
foo = ns['foo']
g = foo() g = foo()
assert issubclass(type(g), typing.Awaitable[int]) assert issubclass(type(g), typing.Awaitable[int])
assert isinstance(g, typing.Awaitable) assert isinstance(g, typing.Awaitable)
@ -1028,6 +1111,7 @@ class CollectionsAbcTests(TestCase):
typing.Awaitable[Manager]) typing.Awaitable[Manager])
g.send(None) # Run foo() till completion, to avoid warning. g.send(None) # Run foo() till completion, to avoid warning.
@skipUnless(PY35, 'Python 3.5 required')
def test_async_iterable(self): def test_async_iterable(self):
base_it = range(10) # type: Iterator[int] base_it = range(10) # type: Iterator[int]
it = AsyncIteratorWrapper(base_it) it = AsyncIteratorWrapper(base_it)
@ -1037,6 +1121,7 @@ class CollectionsAbcTests(TestCase):
typing.AsyncIterable[Employee]) typing.AsyncIterable[Employee])
assert not isinstance(42, typing.AsyncIterable) assert not isinstance(42, typing.AsyncIterable)
@skipUnless(PY35, 'Python 3.5 required')
def test_async_iterator(self): def test_async_iterator(self):
base_it = range(10) # type: Iterator[int] base_it = range(10) # type: Iterator[int]
it = AsyncIteratorWrapper(base_it) it = AsyncIteratorWrapper(base_it)
@ -1127,6 +1212,22 @@ class CollectionsAbcTests(TestCase):
d = MyDict() d = MyDict()
assert isinstance(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): def test_no_set_instantiation(self):
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
typing.Set() typing.Set()
@ -1251,7 +1352,7 @@ class IOTests(TestCase):
return a.readline() return a.readline()
a = stuff.__annotations__['a'] a = stuff.__annotations__['a']
assert a.__parameters__ == (str,) assert a.__parameters__ == ()
def test_binaryio(self): def test_binaryio(self):
@ -1259,7 +1360,7 @@ class IOTests(TestCase):
return a.readline() return a.readline()
a = stuff.__annotations__['a'] a = stuff.__annotations__['a']
assert a.__parameters__ == (bytes,) assert a.__parameters__ == ()
def test_io_submodule(self): def test_io_submodule(self):
from typing.io import IO, TextIO, BinaryIO, __all__, __name__ from typing.io import IO, TextIO, BinaryIO, __all__, __name__
@ -1346,8 +1447,9 @@ class AllTests(TestCase):
assert 'ValuesView' in a assert 'ValuesView' in a
assert 'cast' in a assert 'cast' in a
assert 'overload' in a assert 'overload' in a
assert 'io' in a # Check that io and re are not exported.
assert 're' in a assert 'io' not in a
assert 're' not in a
# Spot-check that stdlib modules aren't exported. # Spot-check that stdlib modules aren't exported.
assert 'os' not in a assert 'os' not in a
assert 'sys' not in a assert 'sys' not in a

View File

@ -1,7 +1,3 @@
# TODO nits:
# Get rid of asserts that are the caller's fault.
# Docstrings (e.g. ABCs).
import abc import abc
from abc import abstractmethod, abstractproperty from abc import abstractmethod, abstractproperty
import collections import collections
@ -56,6 +52,7 @@ __all__ = [
# Concrete collection types. # Concrete collection types.
'Dict', 'Dict',
'DefaultDict',
'List', 'List',
'Set', 'Set',
'NamedTuple', # Not really a type. 'NamedTuple', # Not really a type.
@ -68,12 +65,12 @@ __all__ = [
'no_type_check', 'no_type_check',
'no_type_check_decorator', 'no_type_check_decorator',
'overload', '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): def _qualname(x):
if sys.version_info[:2] >= (3, 3): if sys.version_info[:2] >= (3, 3):
@ -117,8 +114,8 @@ class TypingMeta(type):
""" """
return self return self
def _has_type_var(self): def _get_type_vars(self, tvars):
return False pass
def __repr__(self): def __repr__(self):
return '%s.%s' % (self.__module__, _qualname(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). someone tries to subclass a type alias (not a good idea).
""" """
if (len(args) == 3 and if (len(args) == 3 and
isinstance(args[0], str) and isinstance(args[0], str) and
isinstance(args[1], tuple)): isinstance(args[1], tuple)):
# Close enough. # Close enough.
raise TypeError("A type alias cannot be subclassed") raise TypeError("A type alias cannot be subclassed")
return object.__new__(cls) return object.__new__(cls)
@ -271,8 +268,16 @@ class _TypeAlias:
return issubclass(cls, self.impl_type) return issubclass(cls, self.impl_type)
def _has_type_var(t): def _get_type_vars(types, tvars):
return t is not None and isinstance(t, TypingMeta) and t._has_type_var() 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): 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, At runtime, isinstance(x, T) will raise TypeError. However,
issubclass(C, T) is true for any class C, and issubclass(str, A) issubclass(C, T) is true for any class C, and issubclass(str, A)
and issubclass(bytes, A) are true, and issubclass(int, A) is 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 Type variables may be marked covariant or contravariant by passing
covariant=True or contravariant=True. See PEP 484 for more covariant=True or contravariant=True. See PEP 484 for more
@ -410,8 +415,9 @@ class TypeVar(TypingMeta, metaclass=TypingMeta, _root=True):
self.__bound__ = None self.__bound__ = None
return self return self
def _has_type_var(self): def _get_type_vars(self, tvars):
return True if self not in tvars:
tvars.append(self)
def __repr__(self): def __repr__(self):
if self.__covariant__: 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. T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant.
# A useful type variable with constraints. This represents string types. # A useful type variable with constraints. This represents string types.
# TODO: What about bytearray, memoryview?
AnyStr = TypeVar('AnyStr', bytes, str) AnyStr = TypeVar('AnyStr', bytes, str)
@ -514,12 +519,9 @@ class UnionMeta(TypingMeta):
return self.__class__(self.__name__, self.__bases__, {}, return self.__class__(self.__name__, self.__bases__, {},
p, _root=True) p, _root=True)
def _has_type_var(self): def _get_type_vars(self, tvars):
if self.__union_params__: if self.__union_params__:
for t in self.__union_params__: _get_type_vars(self.__union_params__, tvars)
if _has_type_var(t):
return True
return False
def __repr__(self): def __repr__(self):
r = super().__repr__() r = super().__repr__()
@ -656,12 +658,9 @@ class TupleMeta(TypingMeta):
self.__tuple_use_ellipsis__ = use_ellipsis self.__tuple_use_ellipsis__ = use_ellipsis
return self return self
def _has_type_var(self): def _get_type_vars(self, tvars):
if self.__tuple_params__: if self.__tuple_params__:
for t in self.__tuple_params__: _get_type_vars(self.__tuple_params__, tvars)
if _has_type_var(t):
return True
return False
def _eval_type(self, globalns, localns): def _eval_type(self, globalns, localns):
tp = self.__tuple_params__ tp = self.__tuple_params__
@ -769,12 +768,9 @@ class CallableMeta(TypingMeta):
self.__result__ = result self.__result__ = result
return self return self
def _has_type_var(self): def _get_type_vars(self, tvars):
if self.__args__: if self.__args__:
for t in self.__args__: _get_type_vars(self.__args__, tvars)
if _has_type_var(t):
return True
return _has_type_var(self.__result__)
def _eval_type(self, globalns, localns): def _eval_type(self, globalns, localns):
if self.__args__ is None and self.__result__ is None: if self.__args__ is None and self.__result__ is None:
@ -878,76 +874,106 @@ def _geqv(a, b):
return _gorg(a) is _gorg(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): class GenericMeta(TypingMeta, abc.ABCMeta):
"""Metaclass for generic types.""" """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 __extra__ = None
def __new__(cls, name, bases, namespace, def __new__(cls, name, bases, namespace,
parameters=None, origin=None, extra=None): tvars=None, args=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 = 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: if extra is not None:
self.__extra__ = extra self.__extra__ = extra
# Else __extra__ is inherited, eventually from the # Else __extra__ is inherited, eventually from the
# (meta-)class default above. # (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 return self
def _has_type_var(self): def _get_type_vars(self, tvars):
if self.__parameters__: if self.__origin__ and self.__parameters__:
for t in self.__parameters__: _get_type_vars(self.__parameters__, tvars)
if _has_type_var(t):
return True
return False
def __repr__(self): def __repr__(self):
r = super().__repr__() if self.__origin__ is not None:
if self.__parameters__ is not None: r = repr(self.__origin__)
else:
r = super().__repr__()
if self.__args__:
r += '[%s]' % ( 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__)) ', '.join(_type_repr(p) for p in self.__parameters__))
return r return r
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, GenericMeta): if not isinstance(other, GenericMeta):
return NotImplemented return NotImplemented
return (_geqv(self, other) and if self.__origin__ is not None:
self.__parameters__ == other.__parameters__) return (self.__origin__ is other.__origin__ and
self.__args__ == other.__args__ and
self.__parameters__ == other.__parameters__)
else:
return self is other
def __hash__(self): def __hash__(self):
return hash((self.__name__, self.__parameters__)) return hash((self.__name__, self.__parameters__))
@ -956,37 +982,45 @@ class GenericMeta(TypingMeta, abc.ABCMeta):
if not isinstance(params, tuple): if not isinstance(params, tuple):
params = (params,) params = (params,)
if not 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." msg = "Parameters to generic types must be types."
params = tuple(_type_check(p, msg) for p in params) params = tuple(_type_check(p, msg) for p in params)
if self.__parameters__ is None: if self is Generic:
for p in params: # Generic can only be subscripted with unique type variables.
if not isinstance(p, TypeVar): if not all(isinstance(p, TypeVar) for p in params):
raise TypeError("Initial parameters must be " raise TypeError(
"type variables; got %s" % p) "Parameters to Generic[...] must all be type variables")
if len(set(params)) != len(params): if len(set(params)) != len(params):
raise TypeError( 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: else:
if len(params) != len(self.__parameters__): # Subscripting a regular Generic subclass.
raise TypeError("Cannot change parameter count from %d to %d" % if not self.__parameters__:
(len(self.__parameters__), len(params))) raise TypeError("%s is not a generic class" % repr(self))
for new, old in zip(params, self.__parameters__): alen = len(params)
if isinstance(old, TypeVar): elen = len(self.__parameters__)
if not old.__constraints__: if alen != elen:
# Substituting for an unconstrained TypeVar is OK. raise TypeError(
continue "Too %s parameters for %s; actual %s, expected %s" %
if issubclass(new, Union[old.__constraints__]): ("many" if alen > elen else "few", repr(self), alen, elen))
# Specializing a constrained type variable is OK. tvars = _type_vars(params)
continue args = params
if not issubclass(new, old): return self.__class__(self.__name__,
raise TypeError( (self,) + self.__bases__,
"Cannot substitute %s for %s in %s" %
(_type_repr(new), _type_repr(old), self))
return self.__class__(self.__name__, (self,) + self.__bases__,
dict(self.__dict__), dict(self.__dict__),
parameters=params, tvars=tvars,
args=args,
origin=self, origin=self,
extra=self.__extra__) 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. # C[X] is a subclass of C[Y] iff X is a subclass of Y.
origin = self.__origin__ origin = self.__origin__
if origin is not None and origin is cls.__origin__: if origin is not None and origin is cls.__origin__:
assert len(self.__parameters__) == len(origin.__parameters__) assert len(self.__args__) == len(origin.__parameters__)
assert len(cls.__parameters__) == len(origin.__parameters__) assert len(cls.__args__) == len(origin.__parameters__)
for p_self, p_cls, p_origin in zip(self.__parameters__, for p_self, p_cls, p_origin in zip(self.__args__,
cls.__parameters__, cls.__args__,
origin.__parameters__): origin.__parameters__):
if isinstance(p_origin, TypeVar): if isinstance(p_origin, TypeVar):
if p_origin.__covariant__: if p_origin.__covariant__:
@ -1039,6 +1073,10 @@ class GenericMeta(TypingMeta, abc.ABCMeta):
return issubclass(cls, self.__extra__) return issubclass(cls, self.__extra__)
# Prevent checks for Generic to crash when defining Generic.
Generic = None
class Generic(metaclass=GenericMeta): class Generic(metaclass=GenericMeta):
"""Abstract base class for generic types. """Abstract base class for generic types.
@ -1053,29 +1091,23 @@ class Generic(metaclass=GenericMeta):
This class can then be used as follows:: 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: try:
return mapping[key] return mapping[key]
except KeyError: except KeyError:
return default 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__ = () __slots__ = ()
def __new__(cls, *args, **kwds): def __new__(cls, *args, **kwds):
next_in_mro = object if cls.__origin__ is None:
# Look for the last occurrence of Generic or Generic[...]. return cls.__next_in_mro__.__new__(cls)
for i, c in enumerate(cls.__mro__[:-1]): else:
if isinstance(c, GenericMeta) and _gorg(c) is Generic: origin = _gorg(cls)
next_in_mro = cls.__mro__[i+1] obj = cls.__next_in_mro__.__new__(origin)
return next_in_mro.__new__(_gorg(cls)) obj.__init__(*args, **kwds)
return obj
def cast(typ, val): def cast(typ, val):
@ -1093,9 +1125,7 @@ def _get_defaults(func):
"""Internal helper to extract the default arguments, by name.""" """Internal helper to extract the default arguments, by name."""
code = func.__code__ code = func.__code__
pos_count = code.co_argcount pos_count = code.co_argcount
kw_count = code.co_kwonlyargcount
arg_names = code.co_varnames arg_names = code.co_varnames
kwarg_names = arg_names[pos_count:pos_count + kw_count]
arg_names = arg_names[:pos_count] arg_names = arg_names[:pos_count]
defaults = func.__defaults__ or () defaults = func.__defaults__ or ()
kwdefaults = func.__kwdefaults__ kwdefaults = func.__kwdefaults__
@ -1148,7 +1178,6 @@ def get_type_hints(obj, globalns=None, localns=None):
return hints return hints
# TODO: Also support this as a class decorator.
def no_type_check(arg): def no_type_check(arg):
"""Decorator to indicate that annotations are not type hints. """Decorator to indicate that annotations are not type hints.
@ -1183,8 +1212,42 @@ def no_type_check_decorator(decorator):
return wrapped_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): 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): class _ProtocolMeta(GenericMeta):
@ -1232,14 +1295,16 @@ class _ProtocolMeta(GenericMeta):
break break
else: else:
if (not attr.startswith('_abc_') and if (not attr.startswith('_abc_') and
attr != '__abstractmethods__' and attr != '__abstractmethods__' and
attr != '_is_protocol' and attr != '_is_protocol' and
attr != '__dict__' and attr != '__dict__' and
attr != '__slots__' and attr != '__args__' and
attr != '_get_protocol_attrs' and attr != '__slots__' and
attr != '__parameters__' and attr != '_get_protocol_attrs' and
attr != '__origin__' and attr != '__next_in_mro__' and
attr != '__module__'): attr != '__parameters__' and
attr != '__origin__' and
attr != '__module__'):
attrs.add(attr) attrs.add(attr)
return attrs return attrs
@ -1264,16 +1329,25 @@ class _Protocol(metaclass=_ProtocolMeta):
Hashable = collections_abc.Hashable # Not generic. Hashable = collections_abc.Hashable # Not generic.
class Awaitable(Generic[T_co], extra=collections_abc.Awaitable): if hasattr(collections_abc, 'Awaitable'):
__slots__ = () class Awaitable(Generic[T_co], extra=collections_abc.Awaitable):
__slots__ = ()
else:
Awaitable = None
class AsyncIterable(Generic[T_co], extra=collections_abc.AsyncIterable): if hasattr(collections_abc, 'AsyncIterable'):
__slots__ = ()
class AsyncIterable(Generic[T_co], extra=collections_abc.AsyncIterable):
__slots__ = ()
class AsyncIterator(AsyncIterable[T_co], extra=collections_abc.AsyncIterator): class AsyncIterator(AsyncIterable[T_co],
__slots__ = () extra=collections_abc.AsyncIterator):
__slots__ = ()
else:
AsyncIterable = None
AsyncIterator = None
class Iterable(Generic[T_co], extra=collections_abc.Iterable): class Iterable(Generic[T_co], extra=collections_abc.Iterable):
@ -1332,12 +1406,16 @@ class SupportsRound(_Protocol[T_co]):
pass pass
class Reversible(_Protocol[T_co]): if hasattr(collections_abc, 'Reversible'):
__slots__ = () class Reversible(Iterable[T_co], extra=collections_abc.Reversible):
__slots__ = ()
else:
class Reversible(_Protocol[T_co]):
__slots__ = ()
@abstractmethod @abstractmethod
def __reversed__(self) -> 'Iterator[T_co]': def __reversed__(self) -> 'Iterator[T_co]':
pass pass
Sized = collections_abc.Sized # Not generic. 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. # 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): extra=collections_abc.Mapping):
pass pass
@ -1368,10 +1446,14 @@ class Mapping(Sized, Iterable[KT], Container[KT], Generic[VT_co],
class MutableMapping(Mapping[KT, VT], extra=collections_abc.MutableMapping): class MutableMapping(Mapping[KT, VT], extra=collections_abc.MutableMapping):
pass pass
if hasattr(collections_abc, 'Reversible'):
class Sequence(Sized, Iterable[T_co], Container[T_co], class Sequence(Sized, Reversible[T_co], Container[T_co],
extra=collections_abc.Sequence): 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): class MutableSequence(Sequence[T], extra=collections_abc.MutableSequence):
@ -1436,8 +1518,9 @@ class KeysView(MappingView[KT], AbstractSet[KT],
pass pass
# TODO: Enable Set[Tuple[KT, VT_co]] instead of Generic[KT, VT_co]. class ItemsView(MappingView[Tuple[KT, VT_co]],
class ItemsView(MappingView, Generic[KT, VT_co], Set[Tuple[KT, VT_co]],
Generic[KT, VT_co],
extra=collections_abc.ItemsView): extra=collections_abc.ItemsView):
pass pass
@ -1454,6 +1537,13 @@ class Dict(dict, MutableMapping[KT, VT]):
"use dict() instead") "use dict() instead")
return dict.__new__(cls, *args, **kwds) 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. # Determine what base class to use for Generator.
if hasattr(collections_abc, 'Generator'): if hasattr(collections_abc, 'Generator'):