Skip to content

Commit 01bb4c1

Browse files
authored
Document new @overload functionality (implementations in non-stub files) (#3159)
These are the doc updates corresponding to PR #2603 by @sixolet (specified in a revision of PEP 484: https://www.python.org/dev/peps/pep-0484/#function-method-overloading). The docs got started in PR #PR #2792 by @jkleint.
1 parent 92e3b3e commit 01bb4c1

File tree

1 file changed

+68
-38
lines changed

1 file changed

+68
-38
lines changed

docs/source/function_overloading.rst

+68-38
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,90 @@
1-
Function overloading in stubs
2-
=============================
1+
Function Overloading
2+
====================
33

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

1110
.. code-block:: python
1211
13-
# This is a stub file!
14-
15-
from typing import overload
16-
17-
@overload
18-
def abs(n: int) -> int: pass
19-
20-
@overload
21-
def abs(n: float) -> float: pass
22-
23-
Note that we can't use ``Union[int, float]`` as the argument type,
24-
since this wouldn't allow us to express that the return
25-
type depends on the argument type.
26-
27-
Now if we import ``abs`` as defined in the above library stub, we can
28-
write code like this, and the types are inferred correctly:
12+
from typing import Sequence, TypeVar, Union
13+
T = TypeVar('T')
14+
15+
class MyList(Sequence[T]):
16+
def __getitem__(self, index: Union[int, slice]) -> Union[T, Sequence[T]]:
17+
if isinstance(index, int):
18+
... # Return a T here
19+
elif isinstance(index, slice):
20+
... # Return a sequence of Ts here
21+
else:
22+
raise TypeError(...)
23+
24+
But this is too loose, as it implies that when you pass in an ``int``
25+
you might sometimes get out a single item and sometimes a sequence.
26+
The return type depends on the parameter type in a way that can't be
27+
expressed using a type variable. Instead, we can use `overloading
28+
<https://www.python.org/dev/peps/pep-0484/#function-method-overloading>`_
29+
to give the same function multiple type annotations (signatures) and
30+
accurately describe the function's behavior.
2931

3032
.. code-block:: python
3133
32-
n = abs(-2) # 2 (int)
33-
f = abs(-1.5) # 1.5 (float)
34+
from typing import overload, Sequence, TypeVar, Union
35+
T = TypeVar('T')
36+
37+
class MyList(Sequence[T]):
38+
39+
# The @overload definitions are just for the type checker,
40+
# and overwritten by the real implementation below.
41+
@overload
42+
def __getitem__(self, index: int) -> T:
43+
pass # Don't put code here
44+
45+
# All overloads and the implementation must be adjacent
46+
# in the source file, and overload order may matter:
47+
# when two overloads may overlap, the more specific one
48+
# should come first.
49+
@overload
50+
def __getitem__(self, index: slice) -> Sequence[T]:
51+
pass # Don't put code here
52+
53+
# The implementation goes last, without @overload.
54+
# It may or may not have type hints; if it does,
55+
# these are checked against the overload definitions
56+
# as well as against the implementation body.
57+
def __getitem__(self, index):
58+
# This is exactly the same as before.
59+
if isinstance(index, int):
60+
... # Return a T here
61+
elif isinstance(index, slice):
62+
... # Return a sequence of Ts here
63+
else:
64+
raise TypeError(...)
3465
3566
Overloaded function variants are still ordinary Python functions and
36-
they still define a single runtime object. The following code is
37-
thus valid:
38-
39-
.. code-block:: python
40-
41-
my_abs = abs
42-
my_abs(-2) # 2 (int)
43-
my_abs(-1.5) # 1.5 (float)
67+
they still define a single runtime object. There is no automatic
68+
dispatch happening, and you must manually handle the different types
69+
in the implementation (usually with :func:`isinstance` checks, as
70+
shown in the example).
4471

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

76+
Overloads in stub files are exactly the same, except there is no
77+
implementation.
78+
4979
.. note::
5080

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

5686
.. note::
5787

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

0 commit comments

Comments
 (0)