|
1 |
| -Function overloading in stubs |
2 |
| -============================= |
| 1 | +Function Overloading |
| 2 | +==================== |
3 | 3 |
|
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 can't be |
| 5 | +captured with a simple ``Union``. For example, the ``__getitem__`` (``[]`` bracket |
| 6 | +indexing) method can take an integer and return a single item, or take a ``slice`` |
| 7 | +and return a ``Sequence`` of items. You might be tempted to annotate it like so: |
10 | 8 |
|
11 | 9 | .. code-block:: python
|
12 | 10 |
|
13 |
| - # This is a stub file! |
| 11 | + class Seq(Generic[T], Sequence[T]): |
| 12 | + def __getitem__(self, index: Union[int, slice]) -> Union[T, Sequence[T]]: |
| 13 | + pass |
| 14 | + |
| 15 | +But this is a little loose, as it implies that when you put in an ``int`` you might |
| 16 | +sometimes get out a single item or sometimes a sequence. To capture a constraint |
| 17 | +such as a return type that depends on a parameter type, we can use |
| 18 | +`overloading <https://www.python.org/dev/peps/pep-0484/#function-method-overloading>`_ |
| 19 | +to give the same function multiple type annotations (signatures). |
14 | 20 |
|
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 |
| 21 | +.. code-block:: python |
22 | 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. |
| 23 | + from typing import Generic, Sequence, overload |
| 24 | + T = TypeVar('T') |
26 | 25 |
|
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: |
| 26 | + class Seq(Generic[T], Sequence[T]): |
| 27 | + @overload # These are just for the type checker, and overwritten by the real implementation |
| 28 | + def __getitem__(self, index: int) -> T: |
| 29 | + pass |
29 | 30 |
|
30 |
| -.. code-block:: python |
| 31 | + @overload # All overloads and the implementation must be adjacent in the source file, and overload order may matter |
| 32 | + def __getitem__(self, index: slice) -> Sequence[T]: |
| 33 | + pass |
31 | 34 |
|
32 |
| - n = abs(-2) # 2 (int) |
33 |
| - f = abs(-1.5) # 1.5 (float) |
| 35 | + def __getitem__(self, index): # Actual implementation goes last, and does *not* get type hints or @overload decorator |
| 36 | + if isinstance(index, int): |
| 37 | + ... |
| 38 | + elif isinstance(index, slice): |
| 39 | + ... |
34 | 40 |
|
35 | 41 | 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) |
| 42 | +they still define a single runtime object. There is no multiple dispatch |
| 43 | +happening, and you must manually handle the different types (usually with |
| 44 | +:func:`isinstance` checks). |
44 | 45 |
|
45 | 46 | The overload variants must be adjacent in the code. This makes code
|
46 | 47 | clearer, as you don't have to hunt for overload variants across the
|
47 | 48 | file.
|
48 | 49 |
|
| 50 | +Overloads in stub files are exactly the same, except of course there is no |
| 51 | +implementation. |
| 52 | + |
49 | 53 | .. note::
|
50 | 54 |
|
51 | 55 | As generic type variables are erased at runtime when constructing
|
52 | 56 | instances of generic types, an overloaded function cannot have
|
53 | 57 | variants that only differ in a generic type argument,
|
54 |
| - e.g. ``List[int]`` versus ``List[str]``. |
| 58 | + e.g. ``List[int]`` and ``List[str]``. |
55 | 59 |
|
56 | 60 | .. note::
|
57 | 61 |
|
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`). |
| 62 | + If you just need to constrain a type variable to certain types or subtypes, |
| 63 | + you can use a :ref:`value restriction <type-variable-value-restriction>`). |
0 commit comments