Update PEP 484. Mostly clarifications and edits; dropped predefined platform constants.
This commit is contained in:
parent
cc952ce033
commit
e2ebedeb23
184
pep-0484.txt
184
pep-0484.txt
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue