Skip to content

Commit

Permalink
Merge branch 'overloads-only-in-stubs'
Browse files Browse the repository at this point in the history
Only support @overload in stubs

Closes #563.
  • Loading branch information
JukkaL committed Jan 26, 2015
2 parents 8b01594 + 1909fc1 commit 97731e4
Show file tree
Hide file tree
Showing 14 changed files with 124 additions and 1,270 deletions.
56 changes: 37 additions & 19 deletions docs/source/function_overloading.rst
Original file line number Diff line number Diff line change
@@ -1,28 +1,40 @@
Function overloading
====================
Function overloading in stubs
=============================

You can define multiple instances of a function with the same name but
different signatures. The first matching signature is selected at
runtime when evaluating each individual call. This enables also a form
of multiple dispatch.
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:

.. code-block:: python
# This is a stub file!
from typing import overload
@overload
def abs(n: int) -> int:
return n if n >= 0 else -n
def abs(n: int) -> int: pass
@overload
def abs(n: float) -> float:
return n if n >= 0.0 else -n
def abs(n: float) -> float: pass
Note that we can't use ``Union[int, float]`` as the argument type,
since this wouldn't allow us to express that the the return
type depends on the argument type.

Now if we import ``abs`` as defined in the above library stub, we can
write code like this, and the types are inferred correctly:

.. code-block:: python
abs(-2) # 2 (int)
abs(-1.5) # 1.5 (float)
n = abs(-2) # 2 (int)
f = abs(-1.5) # 1.5 (float)
Overloaded function variants still define a single runtime object; the
following code is valid:
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
Expand All @@ -31,12 +43,18 @@ following code is valid:
my_abs(-1.5) # 1.5 (float)
The overload variants must be adjacent in the code. This makes code
clearer, and otherwise there would be awkward corner cases such as
partially defined overloaded functions that could surprise the unwary
programmer.
clearer, as you don't have to hunt for overload variants across the
file.

.. note::

As generic type variables are erased at runtime, an overloaded
function cannot dispatch based on a generic type argument,
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]``.

.. 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`).
16 changes: 2 additions & 14 deletions docs/source/generics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ example we use the same type variable in two generic functions:
You can also define generic methods — just use a type variable in the
method signature that is different from class type variables.

.. _type-variable-value-restriction:

Type variables with value restriction
*************************************

Expand Down Expand Up @@ -185,20 +187,6 @@ will reject this function:
def union_concat(x: Union[str, bytes], y: Union[str, bytes]) -> Union[str, bytes]:
return x + y # Error: can't concatenate str and bytes
The original, valid definition of ``concat`` is more or less
equivalent to this overloaded function, but it's much shorter,
cleaner and more efficient:

.. code-block:: python
@overload
def overload_concat(x: str, y: str) -> str:
return x + y
@overload
def overload_concat(x: bytes, y: bytes) -> bytes:
return x + y
Another interesting special case is calling ``concat()`` with a
subtype of ``str``:

Expand Down
15 changes: 6 additions & 9 deletions lib-python/3.2/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
from test.support import check_warnings, captured_stdout

from typing import (
Any, Callable, Tuple, List, Sequence, BinaryIO, overload, Traceback, IO,
ducktype
Any, Callable, Tuple, List, Sequence, BinaryIO, Traceback, IO, Union, ducktype, cast
)

import bz2
Expand Down Expand Up @@ -69,24 +68,22 @@ def tearDown(self) -> None:
d = self.tempdirs.pop()
shutil.rmtree(d, os.name in ('nt', 'cygwin'))

@overload
def write_file(self, path: str, content: str = 'xxx') -> None:
def write_file(self, path: Union[str, List[str], tuple], content: str = 'xxx') -> None:
"""Writes a file in the given path.
path can be a string or a sequence.
"""
if isinstance(path, list):
path = os.path.join(*path)
elif isinstance(path, tuple):
path = cast(str, os.path.join(*path))
f = open(path, 'w')
try:
f.write(content)
finally:
f.close()

@overload
def write_file(self, path: Sequence[str], content: str = 'xxx') -> None:
# JLe: work around mypy issue #238
self.write_file(os.path.join(*list(path)), content)

def mkdtemp(self) -> str:
"""Create a temporary directory that will be cleaned up.
Expand Down
Loading

0 comments on commit 97731e4

Please sign in to comment.