Skip to content

Commit

Permalink
Show how to @overload function annotations in user code
Browse files Browse the repository at this point in the history
The docs say `@override` doesn't work in user code, but it seems to work in mypy 0.470.
The update may be waiting on python#2603, but that PR does not seem to include doc updates,
so feel free to put this patch in that PR.
  • Loading branch information
jkleint authored Feb 2, 2017
1 parent ed7d0c0 commit de452ea
Showing 1 changed file with 39 additions and 36 deletions.
75 changes: 39 additions & 36 deletions docs/source/function_overloading.rst
Original file line number Diff line number Diff line change
@@ -1,60 +1,63 @@
Function overloading in stubs
=============================
Function Overloading
====================

Sometimes you have a library function that seems to call for two or
more signatures. That's okay -- you can define multiple *overloaded*
instances of a function with the same name but different signatures in
a stub file (this feature is not supported for user code, at least not
yet) using the ``@overload`` decorator. For example, we can define an
``abs`` function that works for both ``int`` and ``float`` arguments:
Sometimes the types in a function depend on each other in ways that can't be
captured with a simple ``Union``. For example, the ``__getitem__`` (``[]`` bracket
indexing) method can take an integer and return a single item, or take a ``slice``
and return a ``Sequence`` of items. You might be tempted to annotate it like so:

.. code-block:: python
# This is a stub file!
class Seq(Generic[T], Sequence[T]):
def __getitem__(self, index: Union[int, slice]) -> Union[T, Sequence[T]]:
pass
But this is a little loose, as it implies that when you put in an ``int`` you might
sometimes get out a single item or sometimes a sequence. To capture a constraint
such as a return type that depends on a parameter type, we can use
`overloading <https://www.python.org/dev/peps/pep-0484/#function-method-overloading>`_
to give the same function multiple type annotations (signatures).

from typing import overload
@overload
def abs(n: int) -> int: pass
@overload
def abs(n: float) -> float: pass
.. code-block:: python
Note that we can't use ``Union[int, float]`` as the argument type,
since this wouldn't allow us to express that the return
type depends on the argument type.
from typing import Generic, Sequence, overload
T = TypeVar('T')
Now if we import ``abs`` as defined in the above library stub, we can
write code like this, and the types are inferred correctly:
class Seq(Generic[T], Sequence[T]):
@overload # These are just for the type checker, and overwritten by the real implementation
def __getitem__(self, index: int) -> T:
pass
.. code-block:: python
@overload # All overloads and the implementation must be adjacent in the source file, and overload order may matter
def __getitem__(self, index: slice) -> Sequence[T]:
pass
n = abs(-2) # 2 (int)
f = abs(-1.5) # 1.5 (float)
def __getitem__(self, index): # Actual implementation goes last, and does *not* get type hints or @overload decorator
if isinstance(index, int):
...
elif isinstance(index, slice):
...
Overloaded function variants are still ordinary Python functions and
they still define a single runtime object. The following code is
thus valid:

.. code-block:: python
my_abs = abs
my_abs(-2) # 2 (int)
my_abs(-1.5) # 1.5 (float)
they still define a single runtime object. There is no multiple dispatch
happening, and you must manually handle the different types (usually with
:func:`isinstance` checks).

The overload variants must be adjacent in the code. This makes code
clearer, as you don't have to hunt for overload variants across the
file.

Overloads in stub files are exactly the same, except of course there is no
implementation.

.. note::

As generic type variables are erased at runtime when constructing
instances of generic types, an overloaded function cannot have
variants that only differ in a generic type argument,
e.g. ``List[int]`` versus ``List[str]``.
e.g. ``List[int]`` and ``List[str]``.

.. note::

If you are writing a regular module rather than a stub, you can
often use a type variable with a value restriction to represent
functions as ``abs`` above (see :ref:`type-variable-value-restriction`).
If you just need to constrain a type variable to certain types or subtypes,
you can use a :ref:`value restriction <type-variable-value-restriction>`).

0 comments on commit de452ea

Please sign in to comment.