overloading.py
Function overloading for Python 3
overloading
is a module that provides function and method dispatching
based on the types and number of runtime arguments.
When an overloaded function is invoked, the dispatcher compares the supplied arguments to available signatures and calls the implementation providing the most accurate match:
@overload
def biggest(items: Iterable[int]):
return max(items)
@overload
def biggest(items: Iterable[str]):
return max(items, key=len)
>>> biggest([2, 0, 15, 8, 7])
15
>>> biggest(['a', 'abc', 'bc'])
'abc'
Features
- Function validation during registration and comprehensive resolution rules guarantee a well-defined outcome at invocation time.
- Supports the typing module introduced in Python 3.5.
- Supports optional parameters.
- Supports variadic signatures (
*args
and**kwargs
). - Supports class-/staticmethods.
- Evaluates both positional and keyword arguments.
- No dependencies beyond the standard library
Current release
overloading.py v0.5 was released on 16 April 2016.
Notable changes since v0.4
Breaking change notice: The treatment of classmethod
and staticmethod
has been harmonized with that of other decorators. They must now appear after the overloading directive.
- Added support for extended type hints as specified in PEP 484.
- Remaining restrictions on the use of abstract base classes have been lifted.
- The number of exact type matches is no longer a separate factor in function ranking.
- Optional parameters now accept an explicit
None
. - Multiple implementations may now declare
*args
, so the concept of a default implementation no longer exists. - Better decorator support
Installation
pip3 install overloading
To use extended type hints on Python versions prior to 3.5, install the typing module from PyPI:
pip3 install typing
Compatibility
The library is primarily targeted at Python versions 3.3 and above, but Python 3.2 is still supported for PyPy compatibility.
The Travis CI test suite covers CPython 3.3/3.4/3.5 and PyPy3.
Motivation
Why use overloading instead of *args / **kwargs?
- Reduces the need for mechanical argument validation.
- Promotes explicit call signatures, making for cleaner, more self-documenting code.
- Enables the use of full type declarations and type checking tools.
Consider:
class DB:
def get(self, *args):
if len(args) == 1 and isinstance(args[0], Query):
return self.get_by_query(args[0])
elif len(args) == 2:
return self.get_by_id(*args)
else:
raise TypeError(...)
def get_by_query(self, query):
...
def get_by_id(self, id, model):
...
The same thing with overloading:
class DB:
@overload
def get(self, query: Query):
...
@overload
def get(self, id, model):
...
Why use overloading instead of functions with distinct names?
Function names may not always be chosen freely; just consider
__init__()
or any other externally defined interface.Sometimes you just want to expose a single function, particularly when different names don’t add semantic value:
def feed(creature: Human): ... def feed(creature: Dog): ...
vs:
def feed_human(human): ... def feed_dog(dog): ...