From 7c9d8aaf007a448424ed773eb8d36e130518567c Mon Sep 17 00:00:00 2001 From: jkleint Date: Wed, 1 Feb 2017 22:20:22 -0800 Subject: [PATCH 1/3] Show how to @overload function annotations in user code 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 #2603, but that PR does not seem to include doc updates, so feel free to put this patch in that PR. --- docs/source/function_overloading.rst | 75 +++++++++++++++------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/docs/source/function_overloading.rst b/docs/source/function_overloading.rst index b55cddd43fc2..9f907260ba40 100644 --- a/docs/source/function_overloading.rst +++ b/docs/source/function_overloading.rst @@ -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 `_ +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 `). From 4fa13877c9b52b4cf8ff851ed9f7889fe055db61 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 12 Apr 2017 10:28:43 -0700 Subject: [PATCH 2/3] Reformat and update text of comments --- docs/source/function_overloading.rst | 68 ++++++++++++++++++---------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/docs/source/function_overloading.rst b/docs/source/function_overloading.rst index 9f907260ba40..0d5ff3e6a681 100644 --- a/docs/source/function_overloading.rst +++ b/docs/source/function_overloading.rst @@ -1,21 +1,28 @@ Function Overloading ==================== -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: +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 - class Seq(Generic[T], Sequence[T]): + class MyList(Sequence[T]): def __getitem__(self, index: Union[int, slice]) -> Union[T, Sequence[T]]: - pass + if isinstance(index, int): + ... # Return a T here + elif isinstance(index, slice): + ... # Return a sequence of Ts here + else: + assert False, "Unsupported argument %r" % (index,) -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 `_ +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 +`_ to give the same function multiple type annotations (signatures). .. code-block:: python @@ -23,31 +30,43 @@ to give the same function multiple type annotations (signatures). from typing import Generic, Sequence, overload T = TypeVar('T') - class Seq(Generic[T], Sequence[T]): - @overload # These are just for the type checker, and overwritten by the real implementation + class MyList(Sequence[T]): + + # The @overload definitions are just for the type checker, + # and overwritten by the real implementation below. + @overload def __getitem__(self, index: int) -> T: - pass + pass # Don't put code here - @overload # All overloads and the implementation must be adjacent in the source file, and overload order may matter + # All overloads and the implementation must be adjacent + # in the source file, and overload order may matter. + @overload def __getitem__(self, index: slice) -> Sequence[T]: - pass + pass # Don't put code here - def __getitem__(self, index): # Actual implementation goes last, and does *not* get type hints or @overload decorator + # Actual implementation goes last, without @overload. + # It may or may not have type hints; if it does, + # these are checked against the overload definitions + # as well as against the implementation body. + def __getitem__(self, index): + # This is exactly the same as before. if isinstance(index, int): - ... + ... # Return a T here elif isinstance(index, slice): - ... + ... # Return a sequence of Ts here + else: + assert False, "Unsupported argument %r" % (index,) Overloaded function variants are still ordinary Python functions and -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). +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, as shown in the example). 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 +Overloads in stub files are exactly the same, except there is no implementation. .. note:: @@ -59,5 +78,6 @@ implementation. .. note:: - If you just need to constrain a type variable to certain types or subtypes, - you can use a :ref:`value restriction `). + If you just need to constrain a type variable to certain types or + subtypes, you can use a :ref:`value restriction + `. From beebf1ffcf438012689e80be9756aa05cf522ec7 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 12 Apr 2017 12:20:03 -0700 Subject: [PATCH 3/3] Wordsmithing; make examples compile --- docs/source/function_overloading.rst | 39 ++++++++++++++++------------ 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/docs/source/function_overloading.rst b/docs/source/function_overloading.rst index 0d5ff3e6a681..781d8726492d 100644 --- a/docs/source/function_overloading.rst +++ b/docs/source/function_overloading.rst @@ -2,13 +2,16 @@ Function Overloading ==================== 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: +can't be captured with a ``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 + from typing import Sequence, TypeVar, Union + T = TypeVar('T') + class MyList(Sequence[T]): def __getitem__(self, index: Union[int, slice]) -> Union[T, Sequence[T]]: if isinstance(index, int): @@ -16,18 +19,19 @@ and return a single item, or take a ``slice`` and return a elif isinstance(index, slice): ... # Return a sequence of Ts here else: - assert False, "Unsupported argument %r" % (index,) + raise TypeError(...) -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 +But this is too loose, as it implies that when you pass in an ``int`` +you might sometimes get out a single item and sometimes a sequence. +The return type depends on the parameter type in a way that can't be +expressed using a type variable. Instead, we can use `overloading `_ -to give the same function multiple type annotations (signatures). +to give the same function multiple type annotations (signatures) and +accurately describe the function's behavior. .. code-block:: python - from typing import Generic, Sequence, overload + from typing import overload, Sequence, TypeVar, Union T = TypeVar('T') class MyList(Sequence[T]): @@ -39,12 +43,14 @@ to give the same function multiple type annotations (signatures). pass # Don't put code here # All overloads and the implementation must be adjacent - # in the source file, and overload order may matter. + # in the source file, and overload order may matter: + # when two overloads may overlap, the more specific one + # should come first. @overload def __getitem__(self, index: slice) -> Sequence[T]: pass # Don't put code here - # Actual implementation goes last, without @overload. + # The implementation goes last, without @overload. # It may or may not have type hints; if it does, # these are checked against the overload definitions # as well as against the implementation body. @@ -55,12 +61,13 @@ to give the same function multiple type annotations (signatures). elif isinstance(index, slice): ... # Return a sequence of Ts here else: - assert False, "Unsupported argument %r" % (index,) + raise TypeError(...) Overloaded function variants are still ordinary Python functions and -they still define a single runtime object. There is no multiple +they still define a single runtime object. There is no automatic dispatch happening, and you must manually handle the different types -(usually with :func:`isinstance` checks, as shown in the example). +in the implementation (usually with :func:`isinstance` checks, as +shown in the example). 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