A new type hints draft. I've decided to publish more drafts.
Significant changes in this draft: - Define stubs. - Define `@overload`. - Describe `cast()`. - Fix description of `Any`. - Describe `Callable[..., t]`. - Explain why `List[t]` instead of `List<t>`. - Add section on rejected alternatives. - Various other edits for clarity.
This commit is contained in:
parent
5817306884
commit
908c2eb563
530
pep-0484.txt
530
pep-0484.txt
|
@ -8,7 +8,7 @@ Status: Draft
|
|||
Type: Standards Track
|
||||
Content-Type: text/x-rst
|
||||
Created: 29-Sep-2014
|
||||
Post-History: 16-Jan-2015
|
||||
Post-History: 16-Jan-2015,20-Mar-2015
|
||||
Resolution:
|
||||
|
||||
|
||||
|
@ -16,11 +16,33 @@ Abstract
|
|||
========
|
||||
|
||||
This PEP introduces a standard syntax for type hints using annotations
|
||||
on function definitions.
|
||||
(PEP 3107) on function definitions. For example, here is a simple
|
||||
function whose argument and return type are declared in the
|
||||
annotations::
|
||||
|
||||
The proposal is strongly inspired by mypy [mypy]_.
|
||||
def greeting(name: str) -> str:
|
||||
return 'Hello ' + name
|
||||
|
||||
The theory behind type hints and gradual typing is explained in PEP 483.
|
||||
While these annotations are available at runtime through the usual
|
||||
``__annotations__`` attribute, *no type checking happens at runtime*.
|
||||
Instead, the proposal assumes the existence of a separate off-line
|
||||
type checker which users can run over their source code voluntarily.
|
||||
Essentially, such a type checker acts as a very powerful linter.
|
||||
|
||||
The proposal is strongly inspired by mypy [mypy]_. For example, the
|
||||
type "sequence of integers" can be written as ``Sequence[int]``. The
|
||||
square brackets mean that no new syntax needs to be added to the
|
||||
language. The example here uses a custom class ``Sequence``, imported
|
||||
from a pure-Python module ``typing.py``. The ``Sequence[int]``
|
||||
notation works by implementing ``__getitem__()`` in the metaclass.
|
||||
|
||||
The type system supports unions, generic types, and a special type
|
||||
named ``Any`` which is consistent with (i.e. assignable to and from) all
|
||||
types. This latter feature is taken from the idea of gradual typing.
|
||||
Gradual typing and the full type system are explained in PEP 483.
|
||||
|
||||
Other approaches from which we have borrowed or to which ours can be
|
||||
compared and contrasted are described in PEP 482.
|
||||
|
||||
|
||||
Rationale and Goals
|
||||
|
@ -36,6 +58,25 @@ up Python code to easier static analysis and refactoring, potential
|
|||
runtime type checking, and performance optimizations utilizing type
|
||||
information.
|
||||
|
||||
Of these goals, static analysis is the most important. This includes
|
||||
support for off-line type checkers such as mypy, as well as providing
|
||||
a standard notation that can be used by IDEs for code completion and
|
||||
refactoring.
|
||||
|
||||
Non-goals
|
||||
---------
|
||||
|
||||
While the proposed typing module will contain some building blocks for
|
||||
runtime type checking -- in particular a useful ``isinstance()``
|
||||
implementation -- third party packages would have to be developed to
|
||||
implement specific runtime type checking functionality, for example
|
||||
using decorators or metaclasses. Using type hints for performance
|
||||
optimizations is left as an exercise for the reader.
|
||||
|
||||
It should also be emphasized that Python will remain a dynamically
|
||||
typed language, and the authors have no desire to ever make type hints
|
||||
mandatory, even by convention.
|
||||
|
||||
|
||||
Type Definition Syntax
|
||||
======================
|
||||
|
@ -70,7 +111,7 @@ Type aliases are also valid type hints::
|
|||
|
||||
integer = int
|
||||
|
||||
def retry(url: str, retry_count: integer): ...
|
||||
def retry(url: str, retry_count: integer) -> None: ...
|
||||
|
||||
New names that are added to support features described in following
|
||||
sections are available in the ``typing`` package.
|
||||
|
@ -83,17 +124,31 @@ Frameworks expecting callback functions of specific signatures might be
|
|||
type hinted using ``Callable[[Arg1Type, Arg2Type], ReturnType]``.
|
||||
Examples::
|
||||
|
||||
from typing import Any, AnyArgs, Callable
|
||||
from typing import Callable
|
||||
|
||||
def feeder(get_next_item: Callable[[], Item]): ...
|
||||
def feeder(get_next_item: Callable[[], str]) -> None:
|
||||
# Body
|
||||
|
||||
def async_query(on_success: Callable[[int], None], on_error: Callable[[int, Exception], None]): ...
|
||||
def async_query(on_success: Callable[[int], None],
|
||||
on_error: Callable[[int, Exception], None]) -> None:
|
||||
# Body
|
||||
|
||||
def partial(func: Callable[AnyArgs, Any], *args): ...
|
||||
It is possible to declare the return type of a callable without
|
||||
specifying the call signature by substituting a literal ellipsis
|
||||
(three dots) for the list of arguments::
|
||||
|
||||
Since using callbacks with keyword arguments is not perceived as
|
||||
a common use case, there is currently no support for specifying keyword
|
||||
arguments with ``Callable``.
|
||||
def partial(func: Callable[..., str], *args) -> Callable[..., str]:
|
||||
# Body
|
||||
|
||||
Note that there are no square brackets around the ellipsis. The
|
||||
arguments of the callback are completely unconstrained in this case
|
||||
(and keyword arguments are acceptable).
|
||||
|
||||
Since using callbacks with keyword arguments is not perceived as a
|
||||
common use case, there is currently no support for specifying keyword
|
||||
arguments with ``Callable``. Similarly, there is no support for
|
||||
specifying callback signatures with a variable number of argument of a
|
||||
specific type.
|
||||
|
||||
|
||||
Generics
|
||||
|
@ -106,7 +161,7 @@ elements. Example::
|
|||
|
||||
from typing import Mapping, Set
|
||||
|
||||
def notify_by_email(employees: Set[Employee], overrides: Mapping[str, str]): ...
|
||||
def notify_by_email(employees: Set[Employee], overrides: Mapping[str, str]) -> None: ...
|
||||
|
||||
Generics can be parametrized by using a new factory available in
|
||||
``typing`` called ``TypeVar``. Example::
|
||||
|
@ -149,13 +204,13 @@ When a type hint contains names that have not been defined yet, that
|
|||
definition may be expressed as a string, to be resolved later. For
|
||||
example, instead of writing::
|
||||
|
||||
def notify_by_email(employees: Set[Employee]): ...
|
||||
def notify_by_email(employees: Set[Employee]) -> None: ...
|
||||
|
||||
one might write::
|
||||
|
||||
def notify_by_email(employees: 'Set[Employee]'): ...
|
||||
def notify_by_email(employees: 'Set[Employee]') -> None: ...
|
||||
|
||||
.. FIXME: Rigorously define this. Defend it, or find an alternative.
|
||||
.. FIXME: Rigorously define this, and give a motivational example.
|
||||
|
||||
|
||||
Union types
|
||||
|
@ -167,7 +222,7 @@ Example::
|
|||
|
||||
from typing import Union
|
||||
|
||||
def handle_employees(e: Union[Employee, Sequence[Employee]]):
|
||||
def handle_employees(e: Union[Employee, Sequence[Employee]]) -> None:
|
||||
if isinstance(e, Employee):
|
||||
e = [e]
|
||||
...
|
||||
|
@ -180,14 +235,14 @@ One common case of union types are *optional* types. By default,
|
|||
``None`` is an invalid value for any type, unless a default value of
|
||||
``None`` has been provided in the function definition. Examples::
|
||||
|
||||
def handle_employee(e: Union[Employee, None]): ...
|
||||
def handle_employee(e: Union[Employee, None]) -> None: ...
|
||||
|
||||
As a shorthand for ``Union[T1, None]`` you can write ``Optional[T1]``;
|
||||
for example, the above is equivalent to::
|
||||
|
||||
from typing import Optional
|
||||
|
||||
def handle_employee(e: Optional[Employee]): ...
|
||||
def handle_employee(e: Optional[Employee]) -> None: ...
|
||||
|
||||
An optional type is also automatically assumed when the default value is
|
||||
``None``, for example::
|
||||
|
@ -196,14 +251,21 @@ An optional type is also automatically assumed when the default value is
|
|||
|
||||
This is equivalent to::
|
||||
|
||||
def handle_employee(e: Optional[Employee] = None): ...
|
||||
def handle_employee(e: Optional[Employee] = None) -> None: ...
|
||||
|
||||
.. FIXME: Is this really a good idea?
|
||||
The ``Any`` type
|
||||
----------------
|
||||
|
||||
A special kind of union type is ``Any``, a class that responds
|
||||
``True`` to ``issubclass`` of any class. This lets the user
|
||||
explicitly state that there are no constraints on the type of a
|
||||
specific argument or return value.
|
||||
A special kind of type is ``Any``. Every class is a subclass of
|
||||
``Any``. This is also true for the builtin class ``object``.
|
||||
However, to the static type checker these are completely different.
|
||||
|
||||
When the type of a value is ``object``, the type checker will reject
|
||||
almost all operations on it, and assigning it to a variable (or using
|
||||
it as a return value) of a more specialized type is a type error. On
|
||||
the other hand, when a value has type ``Any``, the type checker will
|
||||
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.
|
||||
|
||||
|
||||
Platform-specific type checking
|
||||
|
@ -227,6 +289,8 @@ differences, simple conditionals can be used::
|
|||
else:
|
||||
loop = UnixSelectorEventLoop
|
||||
|
||||
.. FIXME: Also define PY3 and POSIX?
|
||||
|
||||
Arbitrary literals defined in the form of ``NAME = True`` will also be
|
||||
accepted by the type checker to differentiate type resolution::
|
||||
|
||||
|
@ -250,7 +314,7 @@ Compatibility with other uses of function annotations
|
|||
A number of existing or potential use cases for function annotations
|
||||
exist, which are incompatible with type hinting. These may confuse a
|
||||
static type checker. However, since type hinting annotations have no
|
||||
run time behavior (other than evaluation of the annotation expression
|
||||
runtime behavior (other than evaluation of the annotation expression
|
||||
and storing annotations in the ``__annotations__`` attribute of the
|
||||
function object), this does not make the program incorrect -- it just
|
||||
makes it issue warnings when a static analyzer is used.
|
||||
|
@ -258,12 +322,16 @@ makes it issue warnings when a static analyzer is used.
|
|||
To mark portions of the program that should not be covered by type
|
||||
hinting, use the following:
|
||||
|
||||
* a ``@no_type_checks`` decorator on classes and functions
|
||||
* a ``@no_type_check`` decorator on classes and functions
|
||||
|
||||
* a ``# type: ignore`` comment on arbitrary lines
|
||||
|
||||
.. FIXME: should we have a module-wide comment as well?
|
||||
|
||||
.. FIXME: suggest that other uses of annotations be replaced with decorators
|
||||
|
||||
.. FIXME: add reference to "rejected alternatives"
|
||||
|
||||
|
||||
Type Hints on Local and Global Variables
|
||||
========================================
|
||||
|
@ -275,7 +343,7 @@ complex cases, a comment of the following format may be used::
|
|||
x = [] # type: List[Employee]
|
||||
|
||||
In the case where type information for a local variable is needed before
|
||||
if was declared, an ``Undefined`` placeholder might be used::
|
||||
it is declared, an ``Undefined`` placeholder might be used::
|
||||
|
||||
from typing import Undefined
|
||||
|
||||
|
@ -285,17 +353,142 @@ if was declared, an ``Undefined`` placeholder might be used::
|
|||
If type hinting proves useful in general, a syntax for typing variables
|
||||
may be provided in a future Python version.
|
||||
|
||||
Casts
|
||||
=====
|
||||
|
||||
Explicit raised exceptions
|
||||
==========================
|
||||
Occasionally the type checker may need a different kind of hint: the
|
||||
programmer may know that an expression is of a more constrained type
|
||||
than the type checker infers. For example::
|
||||
|
||||
No support for listing explicitly raised exceptions is being defined by
|
||||
this PEP. Currently the only known use case for this feature is
|
||||
documentational, in which case the recommendation is to put this
|
||||
information in a docstring.
|
||||
from typing import List
|
||||
|
||||
def find_first_str(a: List[object]) -> str:
|
||||
index = next(i for i, x in enumerate(a) if isinstance(x, str))
|
||||
# We only get here if there's at least one string in a
|
||||
return cast(str, a[index])
|
||||
|
||||
The type checker infers the type ``object`` for ``a[index]``, 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
|
||||
the type of ``x`` is ``t``. At runtime a cast always returns the
|
||||
expression unchanged -- it does not check the type, and it does not
|
||||
convert or coerce the value.
|
||||
|
||||
Casts differ from type comments (see the previous section). When
|
||||
using a type comment, the type checker should still verify that the
|
||||
inferred type is consistent with the stated type. When using a cast,
|
||||
the type checker trusts the programmer. Also, casts can be used in
|
||||
expressions, while type comments only apply to assignments.
|
||||
|
||||
|
||||
The ``typing`` package
|
||||
Stub Files
|
||||
==========
|
||||
|
||||
Stub files are files containing type hints that are only for use by
|
||||
the type checker, not at runtime. There are several use cases for
|
||||
stub files:
|
||||
|
||||
* Extension modules
|
||||
|
||||
* 3rd party modules whose authors have not yet added type hints
|
||||
|
||||
* Standard library modules for which type hints have not yet been written
|
||||
|
||||
* Modules that must be compatible with Python 2 and 3
|
||||
|
||||
* Modules that use annotations for other purposes
|
||||
|
||||
Stub files have the same syntax as regular Python modules. There is
|
||||
one feature of the ``typing`` module that may only be used in stub
|
||||
files: the ``@overload`` decorator described below.
|
||||
|
||||
The type checker should only check function signatures in stub files;
|
||||
function bodies in stub files should just be a single ``pass`` statement.
|
||||
|
||||
The type checker should have a configurable search path for stub
|
||||
files. If a stub file is found the type checker should not read the
|
||||
corresponding "real" module.
|
||||
|
||||
Stub files may use the ``.py`` extension or alternatively may use the
|
||||
``.pyi`` extension. The latter makes it possible to maintain stub
|
||||
files in the same directory as the corresponding real module.
|
||||
|
||||
Function overloading
|
||||
--------------------
|
||||
|
||||
The ``@overload`` decorator allows describing functions that support
|
||||
multiple different combinations of argument types. This pattern is
|
||||
used frequently in builtin modules and types. For example, the
|
||||
``__getitem__()`` method of the ``bytes`` type can be described as
|
||||
follows::
|
||||
|
||||
from typing import overload
|
||||
|
||||
class bytes:
|
||||
...
|
||||
@overload
|
||||
def __getitem__(self, i: int) -> int: pass
|
||||
@overload
|
||||
def __getitem__(self, s: slice) -> bytes: pass
|
||||
|
||||
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]: pass
|
||||
|
||||
Another example where ``@overload`` comes in handy is the type of the
|
||||
builtin ``map()`` function, which takes a different number of
|
||||
arguments depending on the type of the callable::
|
||||
|
||||
from typing import Callable, Iterable, Iterator, Tuple, TypeVar, overload
|
||||
|
||||
T1 = TypeVar('T1')
|
||||
T2 = TypeVar('T2)
|
||||
S = TypeVar('S')
|
||||
|
||||
@overload
|
||||
def map(func: Callable[[T1], S], iter1: Iterable[T1]) -> Iterator[S]: pass
|
||||
@overload
|
||||
def map(func: Callable[[T1, T2], S],
|
||||
iter1: Iterable[T1], iter2: Iterable[T2]) -> Iterator[S]: pass
|
||||
# ... and we could add more items to support more than two iterables
|
||||
|
||||
Note that we could also easily add items to support ``map(None, ...)``::
|
||||
|
||||
@overload
|
||||
def map(func: None, iter1: Iterable[T1]) -> Iterable[T1]: pass
|
||||
@overload
|
||||
def map(func: None,
|
||||
iter1: Iterable[T1],
|
||||
iter2: Iterable[T2]) -> Iterable[Tuple[T1, T2]]: pass
|
||||
|
||||
The ``@overload`` decorator may only be used in stub files. While it
|
||||
would be possible to provide a multiple dispatch implementation using
|
||||
this syntax, its implementation would require using
|
||||
``sys._getframe()``, which is frowned upon. Also, designing and
|
||||
implementing an efficient multiple dispatch mechanism is hard, which
|
||||
is why previous attempts were abandoned in favor of
|
||||
``functools.singledispatch()``. (See PEP 443, especially its section
|
||||
"Alternative approaches".) In the future we may come up with a
|
||||
satisfactory multiple dispatch design, but we don't want such a design
|
||||
to be constrained by the overloading syntax defined for type hints in
|
||||
stub files.
|
||||
|
||||
|
||||
Exceptions
|
||||
==========
|
||||
|
||||
No syntax for listing explicitly raised exceptions is proposed.
|
||||
Currently the only known use case for this feature is documentational,
|
||||
in which case the recommendation is to put this information in a
|
||||
docstring.
|
||||
|
||||
|
||||
The ``typing`` Package
|
||||
======================
|
||||
|
||||
To open the usage of static type checking to Python 3.5 as well as older
|
||||
|
@ -312,10 +505,17 @@ holds a set of classes representing builtin types with generics, namely:
|
|||
|
||||
* FrozenSet, used as ``FrozenSet[element_type]``
|
||||
|
||||
* Tuple, used as ``Tuple[index0_type, index1_type, ...]``.
|
||||
Arbitrary-length tuples might be expressed using ellipsis, in which
|
||||
case the following arguments are considered the same type as the last
|
||||
defined type on the tuple.
|
||||
* Tuple, used by listing the element types, for example
|
||||
``Tuple[int, int, str]``.
|
||||
Arbitrary-length homogeneous tuples can be expressed
|
||||
using one type and ellipsis, for example ``Tuple[int, ...]``.
|
||||
(The ``...`` here are part of the syntax.)
|
||||
|
||||
The generic versions of concrete collection types (``Dict``, ``List``,
|
||||
``Set``, ``FrozenSet``, and homogeneous arbitrary-length ``Tuple``)
|
||||
are mainly useful for annotating return values. For arguments, prefer
|
||||
the abstract collection types defined below, e.g. ``Mapping``,
|
||||
``Sequence`` or ``AbstractSet``.
|
||||
|
||||
It also introduces factories and helper members needed to express
|
||||
generics and union types:
|
||||
|
@ -333,8 +533,6 @@ generics and union types:
|
|||
|
||||
* Callable, used as ``Callable[[Arg1Type, Arg2Type], ReturnType]``
|
||||
|
||||
* AnyArgs, used as ``Callable[AnyArgs, ReturnType]``
|
||||
|
||||
* AnyStr, equivalent to ``TypeVar('AnyStr', str, bytes)``
|
||||
|
||||
All abstract base classes available in ``collections.abc`` are
|
||||
|
@ -385,7 +583,7 @@ The library includes literals for platform-specific type hinting:
|
|||
|
||||
* WINDOWS
|
||||
|
||||
* UNIXOID, equivalent to ``not WINDOWS``
|
||||
* POSIX, equivalent to ``not WINDOWS``
|
||||
|
||||
The following types are available in the ``typing.io`` module:
|
||||
|
||||
|
@ -408,7 +606,7 @@ modules have two-letter names.").
|
|||
The place of the ``typing`` module in the standard library
|
||||
----------------------------------------------------------
|
||||
|
||||
.. FIXME: complete this section
|
||||
.. FIXME: complete this section (or discard?)
|
||||
|
||||
|
||||
Usage Patterns
|
||||
|
@ -417,8 +615,8 @@ Usage Patterns
|
|||
The main use case of type hinting is static analysis using an external
|
||||
tool without executing the analyzed program. Existing tools used for
|
||||
that purpose like ``pyflakes`` [pyflakes]_ or ``pylint`` [pylint]_
|
||||
might be extended to support type checking. New tools, like mypy's
|
||||
``mypy -S`` mode, can be adopted specifically for this purpose.
|
||||
might be extended to support type checking. New tools, like mypy [mypy]_,
|
||||
can be adopted specifically for this purpose.
|
||||
|
||||
Type checking based on type hints is understood as a best-effort
|
||||
mechanism. In other words, whenever types are not annotated and cannot
|
||||
|
@ -431,20 +629,246 @@ error, the program will continue running.
|
|||
The implementation of a type checker, whether linting source files or
|
||||
enforcing type information during runtime, is out of scope for this PEP.
|
||||
|
||||
.. FIXME: Describe stub modules.
|
||||
.. FIXME: This is somewhat redundant with the updated initial sections.
|
||||
|
||||
.. FIXME: Describe run-time behavior of generic types.
|
||||
|
||||
|
||||
Existing Approaches
|
||||
===================
|
||||
Rejected Alternatives
|
||||
=====================
|
||||
|
||||
PEP 482 lists existing approaches in Python and other languages.
|
||||
During discussion of earlier drafts of this PEP, various objections
|
||||
were raised and alternatives were proposed. We discuss some of these
|
||||
here and explain why we reject them.
|
||||
|
||||
Several main objections were raised.
|
||||
|
||||
Which brackets for generic type parameters?
|
||||
-------------------------------------------
|
||||
|
||||
Most people are familiar with the use of angular brackets
|
||||
(e.g. ``List<int>``) in languages like C++, Java, C# and Swift to
|
||||
express the parametrization of generic types. The problem with these
|
||||
is that they are really hard to parse, especially for a simple-minded
|
||||
parser like Python. In most languages the ambiguities are usually
|
||||
dealy with by only allowing angular brackets in specific syntactic
|
||||
positions, where general expressions aren't allowed. (And also by
|
||||
using very powerful parsing techniques that can backtrack over an
|
||||
arbitrary section of code.)
|
||||
|
||||
But in Python, we'd like type expressions to be (syntactically) the
|
||||
same as other expressions, so that we can use e.g. variable assignment
|
||||
to create type aliases. Consider this simple type expression::
|
||||
|
||||
List<int>
|
||||
|
||||
From the Python parser's perspective, the expression begins with the
|
||||
same four tokens (NAME, LESS, NAME, GREATER) as a chained comparison::
|
||||
|
||||
a < b > c # I.e., (a < b) and (b > c)
|
||||
|
||||
We can even make up an example that could be parsed both ways::
|
||||
|
||||
a < b > [ c ]
|
||||
|
||||
Assuming we had angular brackets in the language, this could be
|
||||
interpreted as either of the following two::
|
||||
|
||||
(a<b>)[c] # I.e., (a<b>).__getitem__(c)
|
||||
a < b > ([c]) # I.e., (a < b) and (b > [c])
|
||||
|
||||
It would surely be possible to come up with a rule to disambiguate
|
||||
such cases, but to most users the rules would feel arbitrary and
|
||||
complex. It would also require us to dramatically change the CPython
|
||||
parser (and every other parser for Python). It should be noted that
|
||||
Python's current parser is intentionally "dumb" -- a simple grammar is
|
||||
easier for users to reason about.
|
||||
|
||||
For all these reasons, square brackets (e.g. ``List[int]``) are (and
|
||||
have long been) the preferred syntax for generic type parameters.
|
||||
They can be implemented by defining the ``__getitem__()`` method on
|
||||
the metaclass, and no new syntax is required at all. This option
|
||||
works in all recent versions of Python (starting with Python 2.2).
|
||||
Python is not alone in this syntactic choice -- generic classes in
|
||||
Scala also use square brackets.
|
||||
|
||||
What about existing uses of annotations?
|
||||
----------------------------------------
|
||||
|
||||
One line of argument points out that PEP 3107 explicitly supports
|
||||
the use of arbitrary expressions in function annotations. The new
|
||||
proposal is then considered incompatible with the specification of PEP
|
||||
3107.
|
||||
|
||||
Our response to this is that, first of all, the current proposal does
|
||||
not introduce any direct incompatibilities, so programs using
|
||||
annotations in Python 3.4 will still work correctly and without
|
||||
prejudice in Python 3.5.
|
||||
|
||||
We do hope that type hints will eventually become the sole use for
|
||||
annotations, but this will require additional discussion and a
|
||||
deprecation period after the initial roll-out of the typing module
|
||||
with Python 3.5. The current PEP will have provisional status (see
|
||||
PEP 411) until Python 3.6 is released. The fastest conceivable scheme
|
||||
would introduce silent deprecation of non-type-hint annotations in
|
||||
3.6, full deprecation in 3.7, and declare type hints as the only
|
||||
allowed use of annotations in Python 3.8. This should give authors of
|
||||
packages that use annotations plenty of time to devise another
|
||||
approach, even if type hints become an overnight success.
|
||||
|
||||
Another possible outcome would be that type hints will eventually
|
||||
become the default meaning for annotations, but that there will always
|
||||
remain an option to disable them. For this purpose the current
|
||||
proposal defines a decorator ``@no_type_check`` which disables the
|
||||
default interpretation of annotations as type hints in a given class
|
||||
or function. It also defines a meta-decorator
|
||||
``@no_type_check_decorator`` which can be used to decorate a decorator
|
||||
(!), causing annotations in any function or class decorated with the
|
||||
latter to be ignored by the type checker.
|
||||
|
||||
There are also ``# type: ignore`` comments, and static checkers should
|
||||
support configuration options to disable type checking in selected
|
||||
packages.
|
||||
|
||||
Despite all these options, proposals have been circulated to allow
|
||||
type hints and other forms of annotations to coexist for individual
|
||||
arguments. One proposal suggests that if an annotation for a given
|
||||
argument is a dictionary literal, each key represents a different form
|
||||
of annotation, and the key ``'type'`` would be use for type hints.
|
||||
The problem with this idea and its variants is that the notation
|
||||
becomes very "noisy" and hard to read. Also, in most cases where
|
||||
existing libraries use annotations, there would be little need to
|
||||
combine them with type hints. So the simpler approach of selectively
|
||||
disabling type hints appears sufficient.
|
||||
|
||||
The problem of forward declarations
|
||||
-----------------------------------
|
||||
|
||||
The current proposal is admittedly sub-optimal when type hints must
|
||||
contain forward references. Python requires all names to be defined
|
||||
by the time they are used. Apart from circular imports this is rarely
|
||||
a problem: "use" here means "look up at runtime", and with most
|
||||
"forward" references there is no problem in ensuring that a name is
|
||||
defined before the function using it is called.
|
||||
|
||||
The problem with type hints is that annotations (per PEP 3107, and
|
||||
similar to default values) are evaluated at the time a function is
|
||||
defined, and thus any names used in an annotation must be already
|
||||
defined when the function is being defined. A common scenario is a
|
||||
class definition whose methods need to reference the class itself in
|
||||
their annotations. (More general, it can also occur with mutually
|
||||
recursive classes.) This is natural for container types, for
|
||||
example::
|
||||
|
||||
class Node:
|
||||
"""Binary tree node."""
|
||||
|
||||
def __init__(self, left: Node, right: None):
|
||||
self.left = left
|
||||
self.right = right
|
||||
|
||||
As written this will not work, because of the peculiarity in Python
|
||||
that class names become defined once the entire body of the class has
|
||||
been executed. Our solution, which isn't particularly elegant, but
|
||||
gets the job done, is to allow using string literals in annotations.
|
||||
Most of the time you won't have to use this though -- most _uses_ of
|
||||
type hints are expected to reference builtin types or types defined in
|
||||
other modules.
|
||||
|
||||
A counterproposal would change the semantics of type hints so they
|
||||
aren't evaluated at runtime at all (after all, type checking happens
|
||||
off-line, so why would type hints need to be evaluated at runtime at
|
||||
all). This of course would run afoul of backwards compatibility,
|
||||
since the Python interpreter doesn't actually know whether a
|
||||
particular annotation is meant to be a type hint or something else.
|
||||
|
||||
The double colon
|
||||
----------------
|
||||
|
||||
A few creative souls have tried to invent solutions for this problem.
|
||||
For example, it was proposed to use a double colon (``::``) for type
|
||||
hints, solving two problems at once: disambiguating between type hints
|
||||
and other annotations, and changing the semantics to preclude runtime
|
||||
evaluation. There are several things wrong with this idea, however.
|
||||
|
||||
* It's ugly. The single colon in Python has many uses, and all of
|
||||
them look familiar because they resemble the use of the colon in
|
||||
English text. This is a general rule of thumb by which Python
|
||||
abides for most forms of punctuation; the exceptions are typically
|
||||
well known from other programming languages. But this use of ``::``
|
||||
is unheard of in English, and in other languages (e.g. C++) it is
|
||||
used as a scoping operator, which is a very different beast. In
|
||||
contrast, the single colon for type hints reads natural -- and no
|
||||
wonder, since it was carefully designed for this purpose (the idea
|
||||
long predates PEP 3107 [gvr-artima]_). It is also used in the same
|
||||
fashion in other languages from Pascal to Swift.
|
||||
|
||||
* What would you do for return type annotations?
|
||||
|
||||
* It's actually a feature that type hints are evaluated at runtime.
|
||||
|
||||
* Making type hints available at runtime allows runtime type
|
||||
checkers to be built on top of type hints.
|
||||
|
||||
* It catches mistakes even when the type checker is not run. Since
|
||||
it is a separate program, users may choose not to run it (or even
|
||||
install it), but might still want to use type hints as a concise
|
||||
form of documentation. Broken type hints are no use even for
|
||||
documentation.
|
||||
|
||||
* Because it's new syntax, using the double colon for type hints would
|
||||
limit them to code that works with Python 3.5 only. By using
|
||||
existing syntax, the current proposal can easily work for older
|
||||
versions of Python 3. (And in fact mypy supports Python 3.2 and
|
||||
newer.)
|
||||
|
||||
* If type hints become successful we may well decide to add new syntax
|
||||
in the future to declare the type for variables, for example
|
||||
``var age: int = 42``. If we were to use a double colon for
|
||||
argument type hints, for consistency we'd have to use the same
|
||||
convention for future syntax, perpetuating the ugliness.
|
||||
|
||||
Other forms of new syntax
|
||||
-------------------------
|
||||
|
||||
A few other forms of alternative syntax have been proposed, e.g. the
|
||||
introduction of a ``where`` keyword [roberge]_, and Cobra-inspired
|
||||
``requires`` clauses. But these all share a problem with the double
|
||||
colon: they won't work for earlier versions of Python 3. The same
|
||||
would apply to a new ``__future__`` import.
|
||||
|
||||
Other backwards compatible conventions
|
||||
--------------------------------------
|
||||
|
||||
The ideas put forward include:
|
||||
|
||||
* A decorator, e.g. ``@typehints(name=str, returns=str)``. This could
|
||||
work, but it's pretty verbose (an extra line, and the argument names
|
||||
must be repeated), and a far cry in elegance from the PEP 3107
|
||||
notation.
|
||||
|
||||
* Stub files. We do want stub files, but they are primarily useful
|
||||
for adding type hints to existing code that doesn't lend itself to
|
||||
adding type hints, e.g. 3rd party packages, code that needs to
|
||||
support both Python 2 and Python 3, and especially extension
|
||||
modules. For most situations, having the annotations in line with
|
||||
the function definitions makes them much more useful.
|
||||
|
||||
* Docstrings. There is an existing convention for docstrings, based
|
||||
on the Sphinx notation (``:type arg1: description``). This is
|
||||
pretty verbose (an extra line per parameter), and not very elegant.
|
||||
We could also make up something new, but the annotation syntax is
|
||||
hard to beat (because it was designed for this very purpose).
|
||||
|
||||
It's also been proposed to simply wait another release. But what
|
||||
problem would that solve? It would just be procrastination.
|
||||
|
||||
|
||||
Is type hinting Pythonic?
|
||||
Is Type Hinting Pythonic?
|
||||
=========================
|
||||
|
||||
.. FIXME: Do we really need this section?
|
||||
|
||||
Type annotations provide important documentation for how a unit of code
|
||||
should be used. Programmers should therefore provide type hints on
|
||||
public APIs, namely argument and return types on functions and methods
|
||||
|
@ -486,6 +910,12 @@ References
|
|||
.. [pylint]
|
||||
http://www.pylint.org
|
||||
|
||||
.. [gvr-artima]
|
||||
http://www.artima.com/weblogs/viewpost.jsp?thread=85551
|
||||
|
||||
.. [roberge]
|
||||
http://aroberge.blogspot.com/2015/01/type-hinting-in-python-focus-on.html
|
||||
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
|
Loading…
Reference in New Issue