Update PEP 484. Mostly clarifications and edits; dropped predefined platform constants.

This commit is contained in:
Guido van Rossum 2015-05-22 08:21:26 -07:00
parent cc952ce033
commit e2ebedeb23
1 changed files with 123 additions and 61 deletions

View File

@ -9,7 +9,7 @@ Status: Draft
Type: Standards Track Type: Standards Track
Content-Type: text/x-rst Content-Type: text/x-rst
Created: 29-Sep-2014 Created: 29-Sep-2014
Post-History: 16-Jan-2015,20-Mar-2015,17-Apr-2015,20-May-2015 Post-History: 16-Jan-2015,20-Mar-2015,17-Apr-2015,20-May-2015,22-May-2015
Resolution: Resolution:
@ -109,8 +109,12 @@ annotations for all arguments and the return type. For a checked
function, the default annotation for arguments and for the return type function, the default annotation for arguments and for the return type
is ``Any``. An exception is that the first argument of instance and is ``Any``. An exception is that the first argument of instance and
class methods does not need to be annotated; it is assumed to have the class methods does not need to be annotated; it is assumed to have the
type of the containing class for instance methods, and ``type`` for type of the containing class for instance methods, and a type object
class methods. type corresponding to the containing class object for class methods.
For example, in class ``A`` the first argument of an instance method
has the implicit type ``A``. In a class method, the precise type of
the first argument cannot be represented using the available type
notation.
(Note that the return type of ``__init__`` ought to be annotated with (Note that the return type of ``__init__`` ought to be annotated with
``-> None``. The reason for this is subtle. If ``__init__`` assumed ``-> None``. The reason for this is subtle. If ``__init__`` assumed
@ -158,7 +162,7 @@ third-party modules).
While annotations are normally the best format for type hints, While annotations are normally the best format for type hints,
there are times when it is more appropriate to represent them there are times when it is more appropriate to represent them
by a special comment, or in a separately distributed interface by a special comment, or in a separately distributed stub
file. (See below for examples.) file. (See below for examples.)
Annotations must be valid expressions that evaluate without raising Annotations must be valid expressions that evaluate without raising
@ -284,6 +288,11 @@ Generics can be parametrized by using a new factory available in
In this case the contract is that the returned value is consistent with In this case the contract is that the returned value is consistent with
the elements held by the collection. the elements held by the collection.
A ``TypeVar()`` expression must always directly be assigned to a
variable (it should not be used as part of a larger expression). The
argument to ``TypeVar()`` must be a string equal to the variable name
to which it is assigned. Type variables must not be redefined.
``TypeVar`` supports constraining parametric types to a fixed set of ``TypeVar`` supports constraining parametric types to a fixed set of
possible types. For example, we can define a type variable that ranges possible types. For example, we can define a type variable that ranges
over just ``str`` and ``bytes``. By default, a type variable ranges over just ``str`` and ``bytes``. By default, a type variable ranges
@ -406,25 +415,57 @@ is not generic but implicitly inherits from ``Iterable[Any]``:
class MyIterable(Iterable): # Same as Iterable[Any] class MyIterable(Iterable): # Same as Iterable[Any]
... ...
Generic metaclasses are not supported.
Instantiating generic classes and type erasure Instantiating generic classes and type erasure
---------------------------------------------- ----------------------------------------------
Generic types like ``List`` or ``Sequence`` cannot be instantiated. Generic types like ``List`` or ``Sequence`` cannot be instantiated.
However, user-defined classes derived from them can be instantiated. However, user-defined classes derived from them can be instantiated.
Given a generic class ``Node[T]`` there are three forms of Suppose we write a ``Node`` class inheriting from ``Generic[T]``::
instantiation:
* ``x = Node()`` -- the type of x is ``Node[Any]``. from typing import TypeVar, Generic
* ``x = Node[T]()`` -- the type of x is ``Node[T]``. T = TypeVar('T')
* ``x = Node[int]()`` -- the type of x is ``Node[int]``. class Node(Generic[T]):
...
At runtime the type is not preserved, and the observable type of x is Now there are two ways we can instantiate this class; the type
just ``Node``. This is type erasure and common practice in languages inferred by a type checker may be different depending on the form we
with generics (e.g. Java, Typescript). use. The first way is to give the value of the type parameter
explicitly -- this overrides whatever type inference the type
checker would otherwise perform:
x = Node[T]() # The type inferred for x is Node[T].
y = Node[int]() # The type inferred for y is Node[int].
If no explicit types are given, the type checker is given some
freedom. Consider this code:
x = Node()
The inferred type could be ``Node[Any]``, as there isn't enough
context to infer a more precise type. Alternatively, a type checker
may reject the line and require an explicit annotation, like this:
x = Node() # type: Node[int] # Inferred type is Node[int].
A type checker with more powerful type inference could look at how
``x`` is used elsewhere in the file and try to infer a more precise
type such as ``Node[int]`` even without an explicit type annotation.
However, it is probably impossible to make such type inference work
well in all cases, since Python programs can be very dynamic.
This PEP doesn't specify the details of how type inference should
work. We allow different tools to experiment with various approaches.
We may give more explicit rules in future revisions.
At runtime the type is not preserved, and the class of ``x`` is just
``Node`` in all cases. This behavior is called "type erasure"; it is
common practice in languages with generics (e.g. Java, TypeScript).
Arbitrary generic types as base classes Arbitrary generic types as base classes
@ -565,28 +606,30 @@ checking is acceptable, a type variable can be declared using
is desirable, pass ``contravariant=True``. At most one of these may is desirable, pass ``contravariant=True``. At most one of these may
be passed. be passed.
A typical example involves defining an immutable container class:: A typical example involves defining an immutable (or read-only)
container class::
from typing import TypeVar from typing import TypeVar, Generic, Iterable, Iterator
T = TypeVar('T', covariant=True) T = TypeVar('T', covariant=True)
class ImmutableList(Generic[T]): class ImmutableList(Generic[T]):
def append(self, T): ... def __init__(self, items: Iterable[T]) -> None: ...
def __iter__(self) -> Iterator[T]: ...
... ...
class Employee: ... class Employee: ...
class Manager(Employee): ... class Manager(Employee): ...
def dump_employees(emps: ImmutableList[Employee]) -> None: ... def dump_employees(emps: ImmutableList[Employee]) -> None:
for emp in emps:
mgrs = ... # type: ImmutableList[Mananger] ...
mgrs.append(Manager())
mgrs = ImmutableList([Manager()]) # type: ImmutableList[Manager]
dump_employees(mgrs) # OK dump_employees(mgrs) # OK
The immutable collection classes in ``typing`` are all defined using a The read-only collection classes in ``typing`` are all defined using a
covariant type variable (e.g. ``Mapping`` and ``Sequence``). The covariant type variable (e.g. ``Mapping`` and ``Sequence``). The
mutable collection classes (e.g. ``MutableMapping`` and mutable collection classes (e.g. ``MutableMapping`` and
``MutableSequence``) are defined using regular invariant type ``MutableSequence``) are defined using regular invariant type
@ -676,7 +719,7 @@ To address this, we write::
self.right = right self.right = right
The string literal should contain a valid Python expression (i.e., The string literal should contain a valid Python expression (i.e.,
``compile(lit, '', 'expr')`` should be a valid code object) and it ``compile(lit, '', 'eval')`` should be a valid code object) and it
should evaluate without errors once the module has been fully loaded. should evaluate without errors once the module has been fully loaded.
The local and global namespace in which it is evaluated should be the The local and global namespace in which it is evaluated should be the
same namespaces in which default arguments to the same function would same namespaces in which default arguments to the same function would
@ -711,8 +754,8 @@ possible to import all the needed models directly::
def bar(self, a: A): ... def bar(self, a: A): ...
# File main.py # File main.py
from a import A from models.a import A
from b import B from models.b import B
Assuming main is imported first, this will fail with an ImportError at Assuming main is imported first, this will fail with an ImportError at
the line ``from models.a import A`` in models/b.py, which is being the line ``from models.a import A`` in models/b.py, which is being
@ -731,8 +774,8 @@ _module_._class_ name::
def bar(self, a: 'a.A'): ... def bar(self, a: 'a.A'): ...
# File main.py # File main.py
from a import A from models.a import A
from b import B from models.b import B
Union types Union types
@ -790,31 +833,26 @@ allow all operations on it, and a value of type ``Any`` can be assigned
to a variable (or used as a return value) of a more constrained type. to a variable (or used as a return value) of a more constrained type.
Predefined constants Version and platform checking
-------------------- -----------------------------
Some predefined Boolean constants are defined in the ``typing`` Type checkers are expected to understand simple version and platform
module to enable platform-specific type definitions and such:: checks, e.g.::
from typing import PY2, PY3, WINDOWS, POSIX import sys
if PY2: if sys.version_info[0] >= 3:
text = unicode # Python 3 specific definitions
else: else:
text = str # Python 2 specific definitions
def f() -> text: ... if sys.platform == 'win32':
# Windows specific definitions
if WINDOWS:
loop = ProactorEventLoop
else: else:
loop = UnixSelectorEventLoop # Posix specific definitions
It is up to the type checker implementation to define their values, as Don't expect a checker to understand obfuscations like
long as ``PY2 == not PY3`` and ``WINDOWS == not POSIX``. When the ``"".join(reversed(sys.platform)) == "xunil"``.
program is being executed these always reflect the current platform,
and this is also the suggested default when the program is being
type-checked.
Default argument values Default argument values
@ -942,7 +980,7 @@ than a type checker may be able to infer. For example::
# We only get here if there's at least one string in a # We only get here if there's at least one string in a
return cast(str, a[index]) return cast(str, a[index])
Some type checkers may not be able to infers that the type of Some type checkers may not be able to infer that the type of
``a[index]`` is ``str`` and only infer ``object`` or ``Any``", but we ``a[index]`` is ``str`` and only infer ``object`` or ``Any``", but we
know that (if the code gets to that point) it must be a string. The know that (if the code gets to that point) it must be a string. The
``cast(t, x)`` call tells the type checker that we are confident that ``cast(t, x)`` call tells the type checker that we are confident that
@ -1021,6 +1059,7 @@ This description is more precise than would be possible using unions
types):: types)::
from typing import Union from typing import Union
class bytes: class bytes:
... ...
def __getitem__(self, a: Union[int, slice]) -> Union[int, bytes]: ... def __getitem__(self, a: Union[int, slice]) -> Union[int, bytes]: ...
@ -1064,6 +1103,35 @@ to be constrained by the overloading syntax defined for type hints in
stub files. In the meantime, using the ``@overload`` decorator or stub files. In the meantime, using the ``@overload`` decorator or
calling ``overload()`` directly raises ``RuntimeError``. calling ``overload()`` directly raises ``RuntimeError``.
A constrained ``TypeVar`` type can often be used instead of using the
``@overload`` decorator. For example, the definitions of ``concat1``
and ``concat2`` in this stub file are equivalent:
from typing import TypeVar
AnyStr = TypeVar('AnyStr', str, bytes)
def concat1(x: AnyStr, y: AnyStr) -> AnyStr: ...
@overload
def concat2(x: str, y: str) -> str: ...
@overload
def concat2(x: bytes, y: bytes) -> bytes: ...
Some functions, such as ``map`` or ``bytes.__getitem__`` above, can't
be represented precisely using type variables. However, unlike
``@overload``, type variables can also be used outside stub files. We
recommend that ``@overload`` is only used in cases where a type
variable is not sufficient, due to its special stub-only status.
Another important difference between type variables such as ``AnyStr``
and using ``@overload`` is that the prior can also be used to define
constraints for generic class type parameters. For example, the type
parameter of the generic class ``typing.IO`` is constrained (only
``IO[str]``, ``IO[bytes]`` and ``IO[Any]`` are valid):
class IO(Generic[AnyStr]): ...
Storing and distributing stub files Storing and distributing stub files
----------------------------------- -----------------------------------
@ -1162,6 +1230,8 @@ Fundamental building blocks:
* TypeVar, used as ``X = TypeVar('X', Type1, Type2, Type3)`` or simply * TypeVar, used as ``X = TypeVar('X', Type1, Type2, Type3)`` or simply
``Y = TypeVar('Y')`` (see above for more details) ``Y = TypeVar('Y')`` (see above for more details)
* Generic, used to create user-defined generic classes
Generic variants of builtin collections: Generic variants of builtin collections:
* Dict, used as ``Dict[key_type, value_type]`` * Dict, used as ``Dict[key_type, value_type]``
@ -1242,18 +1312,10 @@ A few one-off types are defined that test for single special methods
* SupportsBytes, to test for ``__bytes__`` * SupportsBytes, to test for ``__bytes__``
Constants for platform-specific type hinting:
* PY2
* PY3, equivalent to ``not PY2``
* WINDOWS
* POSIX, equivalent to ``not WINDOWS``
Convenience definitions: Convenience definitions:
* Optional, defined by ``Optional[t] == Union[t, type(None)]``
* AnyStr, defined as ``TypeVar('AnyStr', str, bytes)`` * AnyStr, defined as ``TypeVar('AnyStr', str, bytes)``
* NamedTuple, used as * NamedTuple, used as