Skip to content

Docs: show how to @overload function annotations in user code #2792

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generic[T] here and below are redundant.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such long comments will look a bit ugly on readthedocs. I would recommend wrapping them or shortening to approx. 80 chars.

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>`).