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