blob: 791a9f3e4269d7e6b84fbe48a345f76985d5b2af [file] [log] [blame]
PEP: NNN
Title: Type Hints
Version: $Revision$
Last-Modified: $Date$
Author: Guido van Rossum <guido@python.org>, Jukka Lehtosalo <jukka.lehtosalo@iki.fi>, Łukasz Langa <lukasz@langa.pl>
Discussions-To: Python-Dev <python-dev@python.org>
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 12-Apr-2014
Post-History: 12-Apr-2014
Replaces: 3107
Resolution:
Abstract
========
This PEP introduces a standard syntax for type hints using annotations
on function definitions.
Rationale and Goals
===================
PEP 3107 [#pep-3107]_ added support for arbitrary annotations on parts
of a function definition. Although no meaning has been assigned to
those metadata then, there has always been an implicit goal to use them
for type hinting, which is listed as the first possible use case in the
said PEP.
This PEP aims to provide a standard syntax for type annotations, opening
up Python code to easier static analysis and refactoring, potential
runtime type checking, and performance optimizations utilizing type
information.
Type Definition Syntax
======================
The syntax leverages PEP 3107-style annotations with a number of
extensions described in sections below. In its basic form, type hinting
is used by filling function annotations with classes::
def greeting(name: str) -> str:
return 'Hello, {}'.format(name)
.. FIXME: Bad example because of `.format()`, also, doesn't sell type
hinting enough right away.
This denotes that the expected type of the ``name`` argument is ``str``.
Analogically, the expected return type is ``str``. Subclasses of
a specified argument type are also accepted as valid types for that
argument.
Abstract base classes, types available in the ``types`` module, as well
as any user-built class, can be provided as type hints as well. In any
case, the type name used has to be available in the local scope by means
of import or direct declaration.
As a singleton, the ``None`` object is considered equivalent to
``NoneType`` for type hinting purposes.
Type aliases are also valid type hints::
integer = int
def retry(func: types.FunctionType, retry_count: integer): ...
While the function annotation syntax permits any arbitrary expression to
be passed as an annotation, the use of runtime-evaluated annotations is
discouraged as it limits what static analysis can infer from such
expressions. Constant, direct type expressions are recommended.
Generics
--------
Since type information about objects kept in containers cannot be
statically inferred in a generic way, abstract base classes have been
extended to support subscription to denote expected types for container
elements. Example::
def notify_by_email(employees: Set[Employee], overrides: Mapping[str, str]): ...
Internally, a ``__getitem__`` operation on a type simply returns the
said type::
>>> print(MutableMapping)
<class 'collections.abc.MutableMapping'>
>>> print(MutableMapping[int])
<class 'collections.abc.MutableMapping'>
.. FIXME: Not true, we'd actually like for MutableMapping[int] to return
a new class object that represents a MutableMappingOfInts
Generics can be parametrized by using a new factory available in
``collections.abc`` called ``Var``. Example::
T = Var('T') # Declare type variable
def first(l: Sequence[T]) -> T: # Generic function
return l[0]
In this case the contract is that the returning value is consistent with
the elements held by the collection. Using the same var-type under
multiple names is considered aliasing. The following example is
equivalent to the one above::
X = Var('X')
Y = Var('X')
def first(l: Sequence[X]) -> Y: # Generic function
return l[0]
``Var`` supports constraining parametric types to classes with any of
the specified bases. Example::
X = Var('X')
Y = Var('Y', Iterable[X])
def filter(rule: Callable, collection: Y) -> Y:
...
.. FIXME: fill in the implementation and the Callable signature
definition; add an example with multiple bases defined
In the example above we specify that ``Y`` can be any subclass of
Iterable with elements of type ``X``, as long as the return type of
``filter()`` will be the same as the type of the ``collection``
argument. That means during type checking both ``X`` and ``Y`` must
point to the same concrete types in the ``collection`` argument as well
as the function's return value. Union types can be used in ``base``.
As with function arguments, generics are covariant, which is in spirit
of duck typing.
Forward references
------------------
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]): ...
one might write::
def notify_by_email(employees: 'Set[Employee]'): ...
Union types
-----------
Since accepting a small, limited set of expected types for a single
argument is common, the ``collections.abc`` module has been extended
with a new special factory called ``Union``. Example::
def handle_employee(e: Union[Employee, Sequence[Employee]]):
if isinstance(e, Employee):
e = [e]
...
A type factored by ``Union[T1, T2, ...]`` responds ``True`` to
``issubclass`` checks for ``T1`` and any of its subclasses, ``T2`` and
any of its subclasses, and so on.
One common case of union types are *optional* types, also called
*nullable* 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: Employee = None): ...
Union types might be inferred implicitly from the function body::
if x:
y = None # First definition of y
else:
y = 'a' # Second definition of y
Since this case may or may not be correct, the linter is expected to
generate warnings.
A specific kind of a union type is ``Any``, a class in
``collections.abc`` 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.
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. If a single scalar
annotation is provided, it is assumed to describe the type of the
argument::
def notify_by_email(employees: List[Employee]): ...
However, if a dictionary is provided, typing information is available
under the ``'type'`` key, with other keys open for other use cases::
def notify_by_email(employees: {'type': List[Employee], 'min_size': 1, 'max_size': 100}):
...
Type Hints on Local and Global Variables
========================================
No first-class syntax support for explicitly marking variables as being
of a specific type is added by this PEP. To help with type inference in
complex cases, a comment of the following format might be used::
x = [] # type: List[Employee]
If type hinting proves useful in general, variable typing in syntax will
be provided in a future Python version.
Explicit raised exceptions
==========================
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.
The ``typing`` module
=====================
To enable generics on builtin types, a set of classes is introduced in
a new module called ``typing``. Those classes are as follows:
* Dict, used as ``Dict[key_type, value_type]``
* List, used as ``List[element_type]``
* Set, used as ``Set[element_type]``. See remark in ``AbstractSet``
below.
* 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.
Subscripting the above types returns the vanilla type at runtime, just
as in the case of abstract base classes.
To open the usage of static type checking to Python 3 versions older
than 3.5, the new and modified types found in the ``collections.abc``
module are also importable from the ``typing`` module. The following
new members:
* Any
* Union
* Var
All available abstract base classes are importable:
* ByteString
* Callable
* Container
* Hashable
* ItemsView
* Iterable
* Iterator
* KeysView
* Mapping
* MappingView
* MutableMapping
* MutableSequence
* MutableSet
* Sequence
* Set as ``AbstractSet``. This name change was required because ``Set``
in the ``typing`` module means ``set()`` with generics.
* Sized
* ValuesView
* Mapping
* IO
* BinaryIO
* TextIO
The following helper types are also provided by the ``typing`` module:
* AnyStr, equivalent to ``Var('AnyStr', str, bytes)``
* Match and Pattern, types of ``re.match()`` and ``re.compile()`` results
The module also provides a special source encoding that enables usage of
function annotations in Python 2.7.
The place of the ``typing`` module in the standard library
----------------------------------------------------------
.. FIXME: complete this section
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.
Type checking based on type hints is understood as a best-effort
mechanism. In other words, whenever types are not annotated and cannot
be inferred, the type checker considers such code valid. Type errors
are only reported in case of explicit or inferred conflict. Moreover,
as a mechanism that is not tied to execution of the code, it does not
affect runtime behaviour. In other words, even in the case of a typing
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.
Existing Approaches in Python
=============================
Cython and Numba
----------------
Numba [#numba]_ is a *just-in-time* specializing compiler producing
optimized native code from annotated Python and NumPy code.
obiwan
------
obiwan [#obiwan]_ is a library enabling runtime type checking inspired
by TypeScript [#typescript]_ (see `Existing Approaches in Other
Languages <#typescript>`_). The syntax leverages function annotations,
extending it to validate callback functions, elements of dictionaries
and lists. Type checkers might be functions, in which case a type is
considered valid if the type checker returns True.
Examples::
def divide(a: int, b: float) -> number:
return a/b
def robodial(person: {"name":str, "phone": {"type":str, "number":str}}):
...
def on_success(callback: function(int, any, ...)):
...
pytypedecl
----------
pytypedecl [#pytypedecl]_ consists of a type declaration language for
Python and an optional runtime type checker. Type declarations for
``module.py`` are kept in a separate file called ``module.pytd``. This
solves issues with declaration ordering.
While initially inspired by the PEP 3107 syntax, pytypedecl diverged to
support the following: overloading (specifying the same function
multiple times with different argument types), union types (listing
multiple possible types for a single argument), generics for
collections, and exceptions raised (for documentation purposes).
Example::
class Logger:
def log(messages: list<str>, buffer: Readable or Writeable) raises IOError
def log(messages: list<str>) -> None
def setStatus(status: int or str)
Argument Clinic
---------------
Argument Clinic [#argumentclinic]_ is a preprocessor for CPython
C files, automating maintenance of argument parsing code for “builtins”.
Example argument declaration::
/*[clinic input]
os.chmod
path: path_t(allow_fd='PATH_HAVE_FCHMOD')
Path to be modified. May always be specified as a str or bytes.
mode: int
Operating-system mode bitfield.
*
dir_fd : dir_fd(requires='fchmodat') = None
If not None, it should be a file descriptor open to a dir, and
path should be relative; path will then be relative to that
dir.
follow_symlinks: bool = True
If False, and the last element of the path is a symlink, chmod
will modify the symlink itself instead of the file the link
points to.
Change the access permissions of a file.
[clinic start generated code]*/
NumPy
-----
NumPy [#numpy]_ is an extension to Python adding support for
multi-dimensional arrays, matrices and operations to operate on those.
The project requires typing information in the API documentation. There
is an unambiguous syntax for that type of documentation. Example
documentation with types::
ndarray.item(*args)
Copy an element of an array to a standard Python scalar and return it.
Parameters
----------
\\*args : Arguments (variable number and type)
* none: in this case, the method only works for arrays
with one element (`a.size == 1`), which element is
copied into a standard Python scalar object and returned.
* int_type: this argument is interpreted as a flat index into
the array, specifying which element to copy and return.
* tuple of int_types: functions as does a single int_type argument,
except that the argument is interpreted as an nd-index into the
array.
Returns
-------
z : Standard Python scalar object
A copy of the specified element of the array as a suitable
Python scalar
Existing Approaches in Other Languages
======================================
ActionScript
------------
ActionScript [#actionscript]_ is a class-based, single inheritance,
object-oriented superset of ECMAScript. It supports inferfaces and
strong runtime-checked static typing. Compilation supports a “strict
dialect” where type mismatches are reported at compile-time.
Example code with types::
package {
import flash.events.Event;
public class BounceEvent extends Event {
public static const BOUNCE:String = "bounce";
private var _side:String = "none";
public function get side():String {
return _side;
}
public function BounceEvent(type:String, side:String){
super(type, true);
_side = side;
}
public override function clone():Event {
return new BounceEvent(type, _side);
}
}
}
Dart
----
Dart [#dart]_ is a class-based, single inheritance, object-oriented
language with C-style syntax. It supports interfaces, abstract classes,
reified generics, and optional typing.
Types are inferred when possible. The runtime differentiates between two
modes of execution: *checked mode* aimed for development (catching type
errors at runtime) and *production mode* recommended for speed execution
(ignoring types and asserts).
Example code with types::
class Point {
final num x, y;
Point(this.x, this.y);
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return math.sqrt(dx * dx + dy * dy);
}
}
Hack
----
Hack [#hack]_ is a programming language that interoperates seamlessly
with PHP. It provides opt-in static type checking, type aliasing,
generics, nullable types, and lambdas.
Example code with types::
<?hh
class MyClass {
private ?string $x = null;
public function alpha(): int {
return 1;
}
public function beta(): string {
return 'hi test';
}
}
function f(MyClass $my_inst): string {
// Will generate a hh_client error
return $my_inst->alpha();
}
TypeScript
----------
TypeScript [#typescript]_ is a typed superset of JavaScript that adds
interfaces, classes, mixins and modules to the language.
Type checks are duck typed. Multiple valid function signatures are
specified by supplying overloaded function declarations. Functions and
classes can use generics as type parametrization. Interfaces can have
optional fields. Interfaces can specify array and dictionary types.
Classes can have constructors that implicitly add arguments as fields.
Classes can have static fields. Classes can have private fields.
Classes can have getters/setters for fields (like property). Types are
inferred.
Example code with types::
interface Drivable {
start(): void;
drive(distance: number): boolean;
getPosition(): number;
}
class Car implements Drivable {
private _isRunning: boolean;
private _distanceFromStart: number;
constructor() {
this._isRunning = false;
this._distanceFromStart = 0;
}
public start() {
this._isRunning = true;
}
public drive(distance: number): boolean {
if (this._isRunning) {
this._distanceFromStart += distance;
return true;
}
return false;
}
public getPosition(): number {
return this._distanceFromStart;
}
}
Is type hinting Pythonic?
=========================
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
considered public. However, because types of local and global variables
can be often inferred, they are rarely necessary.
The kind of information that type hints hold has always been possible to
achieve by means of docstrings. In fact, a number of formalized
mini-languages for describing accepted arguments have evolved. Moving
this information to the function declaration makes it more visible and
easier to access both at runtime and by static analysis. Adding to that
the notion that “explicit is better than implicit”, type hints are
indeed *Pythonic*.
Acknowledgements
================
Influences include all mentioned existing languages, libraries and
frameworks. Many thanks to their creators, in alphabetical order:
Stefan Behnel, William Edwards, Greg Ewing, Larry Hastings, Anders
Hejlsberg, Alok Menghrajani, Travis E. Oliphant, Joe Pamer,
Raoul-Gabriel Urma, and Julien Verlaguet.
I'd also like to thank Radomir Dopieralski for suggesting warnings as
the toggle for runtime checks.
References
==========
.. [#pep-3107]
http://www.python.org/dev/peps/pep-3107/
.. [#mypy]
https://github.com/JukkaL/mypy
.. [#obiwan]
http://pypi.python.org/pypi/obiwan
.. [#numba]
http://numba.pydata.org
.. [#pytypedecl]
https://github.com/google/pytypedecl
.. [#argumentclinic]
https://docs.python.org/3/howto/clinic.html
.. [#numpy]
http://www.numpy.org
.. [#typescript]
http://www.typescriptlang.org/
.. [#hack]
http://hacklang.org/
.. [#dart]
https://www.dartlang.org/
.. [#actionscript]
http://livedocs.adobe.com/specs/actionscript/3/
Copyright
=========
This document has been placed in the public domain.
..
Local Variables:
mode: indented-text
indent-tabs-mode: nil
sentence-end-double-space: t
fill-column: 70
coding: utf-8
End: