|
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