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
Content-Type: text/x-rst
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:
@ -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
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
type of the containing class for instance methods, and ``type`` for
class methods.
type of the containing class for instance methods, and a type object
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
``-> 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,
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.)
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
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
possible types. For example, we can define a type variable that 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]
...
Generic metaclasses are not supported.
Instantiating generic classes and type erasure
----------------------------------------------
Generic types like ``List`` or ``Sequence`` cannot be instantiated.
However, user-defined classes derived from them can be instantiated.
Given a generic class ``Node[T]`` there are three forms of
instantiation:
Suppose we write a ``Node`` class inheriting from ``Generic[T]``::
* ``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
just ``Node``. This is type erasure and common practice in languages
with generics (e.g. Java, Typescript).
Now there are two ways we can instantiate this class; the type
inferred by a type checker may be different depending on the form we
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
@ -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
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)
class ImmutableList(Generic[T]):
def append(self, T): ...
def __init__(self, items: Iterable[T]) -> None: ...
def __iter__(self) -> Iterator[T]: ...
...
class Employee: ...
class Manager(Employee): ...
def dump_employees(emps: ImmutableList[Employee]) -> None: ...
mgrs = ... # type: ImmutableList[Mananger]
mgrs.append(Manager())
def dump_employees(emps: ImmutableList[Employee]) -> None:
for emp in emps:
...
mgrs = ImmutableList([Manager()]) # type: ImmutableList[Manager]
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
mutable collection classes (e.g. ``MutableMapping`` and
``MutableSequence``) are defined using regular invariant type
@ -676,7 +719,7 @@ To address this, we write::
self.right = right
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.
The local and global namespace in which it is evaluated should be the
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): ...
# File main.py
from a import A
from b import B
from models.a import A
from models.b import B
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
@ -731,8 +774,8 @@ _module_._class_ name::
def bar(self, a: 'a.A'): ...
# File main.py
from a import A
from b import B
from models.a import A
from models.b import B
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.
Predefined constants
--------------------
Version and platform checking
-----------------------------
Some predefined Boolean constants are defined in the ``typing``
module to enable platform-specific type definitions and such::
Type checkers are expected to understand simple version and platform
checks, e.g.::
from typing import PY2, PY3, WINDOWS, POSIX
import sys
if PY2:
text = unicode
if sys.version_info[0] >= 3:
# Python 3 specific definitions
else:
text = str
# Python 2 specific definitions
def f() -> text: ...
if WINDOWS:
loop = ProactorEventLoop
if sys.platform == 'win32':
# Windows specific definitions
else:
loop = UnixSelectorEventLoop
# Posix specific definitions
It is up to the type checker implementation to define their values, as
long as ``PY2 == not PY3`` and ``WINDOWS == not POSIX``. When the
program is being executed these always reflect the current platform,
and this is also the suggested default when the program is being
type-checked.
Don't expect a checker to understand obfuscations like
``"".join(reversed(sys.platform)) == "xunil"``.
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
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
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
@ -1010,20 +1048,21 @@ follows::
from typing import overload
class bytes:
...
@overload
def __getitem__(self, i: int) -> int: ...
@overload
def __getitem__(self, s: slice) -> bytes: ...
...
@overload
def __getitem__(self, i: int) -> int: ...
@overload
def __getitem__(self, s: slice) -> bytes: ...
This description is more precise than would be possible using unions
(which cannot express the relationship between the argument and return
types)::
from typing import Union
class bytes:
...
def __getitem__(self, a: Union[int, slice]) -> Union[int, bytes]: ...
...
def __getitem__(self, a: Union[int, slice]) -> Union[int, bytes]: ...
Another example where ``@overload`` comes in handy is the type of the
builtin ``map()`` function, which takes a different number of
@ -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
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
-----------------------------------
@ -1162,6 +1230,8 @@ Fundamental building blocks:
* TypeVar, used as ``X = TypeVar('X', Type1, Type2, Type3)`` or simply
``Y = TypeVar('Y')`` (see above for more details)
* Generic, used to create user-defined generic classes
Generic variants of builtin collections:
* 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__``
Constants for platform-specific type hinting:
* PY2
* PY3, equivalent to ``not PY2``
* WINDOWS
* POSIX, equivalent to ``not WINDOWS``
Convenience definitions:
* Optional, defined by ``Optional[t] == Union[t, type(None)]``
* AnyStr, defined as ``TypeVar('AnyStr', str, bytes)``
* NamedTuple, used as