diff options
author | Guido van Rossum <guido@python.org> | 2016-04-05 15:28:52 (GMT) |
---|---|---|
committer | Guido van Rossum <guido@python.org> | 2016-04-05 15:28:52 (GMT) |
commit | bd5b9a074241c6146bdcacef9a1e84f48c7c8d8a (patch) | |
tree | 019b5a1657f7b4a9a78f18daf5449a89c882539c /Lib/typing.py | |
parent | 0f7673943a4ad633c59071b24b1f170a0df442c3 (diff) | |
download | cpython-bd5b9a074241c6146bdcacef9a1e84f48c7c8d8a.zip cpython-bd5b9a074241c6146bdcacef9a1e84f48c7c8d8a.tar.gz cpython-bd5b9a074241c6146bdcacef9a1e84f48c7c8d8a.tar.bz2 |
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).
Diffstat (limited to 'Lib/typing.py')
-rw-r--r-- | Lib/typing.py | 394 |
1 files changed, 242 insertions, 152 deletions
diff --git a/Lib/typing.py b/Lib/typing.py index 823f9be..de2a462 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) -class GenericMeta(TypingMeta, abc.ABCMeta): - """Metaclass for generic types.""" +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 - # 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.) +class GenericMeta(TypingMeta, abc.ABCMeta): + """Metaclass for generic types.""" __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 + tvars=None, args=None, origin=None, extra=None): + self = super().__new__(cls, name, bases, namespace, _root=True) + + if tvars is not None: + # Called from __getitem__() below. + assert origin is not None + 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 isinstance(base, TypingMeta): - if not isinstance(base, GenericMeta): + 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( - "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 + "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'): |