overloading.py

Function overloading for Python 3

Build Status Coverage Status

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):
        ...