Python-Overloading.py: Function overloading for Python 3

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

Documentation

The full documentation is available at https://overloading.readthedocs.org/.

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.

Comments

  • Overloading and performance
    Overloading and performance

    May 1, 2015

    Do this implementation have known drawback impact on performance?

    Thanks for this wonderful module. jvtrudel

    Reply
  • Is there a way to override-overload a function?
    Is there a way to override-overload a function?

    Jul 14, 2016

    That's mostly relevant for interactive usecases such as REPL or a Jupyter Notebook.

    Suppose I have a function

    def f(a: int):
        return a
    

    and at some point (in an interactive session, with ^ already declared), want to change it, to, for i.,

    def f(a: int):
        return a * 2
    

    This would fail as of overloading==0.5.0 with OverloadingError: Failed to overload function 'f': non-unique signature. Can I somehow force an override?

    Reply
  • Convert readthedocs links for their .org -> .io migration for hosted projects
    Convert readthedocs links for their .org -> .io migration for hosted projects

    Jan 2, 2017

    As per their blog post of the 27th April ‘Securing subdomains’:

    Starting today, Read the Docs will start hosting projects from subdomains on the domain readthedocs.io, instead of on readthedocs.org. This change addresses some security concerns around site cookies while hosting user generated data on the same domain as our dashboard.

    Test Plan: Manually visited all the links I’ve modified.

    Reply
  • AttributeError: module 'typing' has no attribute 'UnionMeta'
    AttributeError: module 'typing' has no attribute 'UnionMeta'

    Mar 18, 2017

    While running the following:

    #! /usr/bin/env python3.6
    # -*- coding: utf-8 -*-
    
    from overloading import overload, overloaded, overloads
    from unittest import TestCase, main
    from typing import Union, Dict, Any, Iterable
    
    
    class TestOverloading(TestCase):
        # Use overloading on functions with different types
    
        _add_types = Union[int, str, dict, set]
    
        @overload
        @staticmethod
        def add(x: int, y: int) -> int:
            return x + y
    
        @overload
        @staticmethod
        def add(x: str, y: str) -> str:
            return x + y
    
        @overload
        @staticmethod
        def add(x: Dict[Any, Any], y: Dict[Any, Any]) -> Dict[Any, Any]:
            return {**x, **y}
    
        @overload
        @staticmethod
        def add(x: set, y: set) -> set:
            return x | y
    
        @staticmethod
        def complete_add(x: _add_types, y: _add_types) -> _add_types:
    
            class TypeMismatchError(TypeError):
                pass
    
            if type(x) != type(y):
                raise TypeMismatchError(
                    f'complete_add expected arguments of similar type, received {type(x)}, {type(y)}'
                )
    
            if (type(x) is int and type(y) is int):
                return x + y
    
            elif type(x) is str and type(y) is str:
                return x + y
    
            elif type(x) is dict and type(y) is dict:
                return {**x, **y}
    
            elif type(x) is set and type(y) is set:
                return x | y
    
            else:
                ts = ', '.join(_add_types.__args__)
                raise TypeError(
                    f'complete_add expected arguments of type {ts} received {type(x)}'
                )
    
        def test1(self) -> None:
    
            self.assertEqual(self.add(1, 2), self.complete_add(1, 2))
            self.assertEqual(self.add('Brandon', ' Doyle'), self.complete_add('Brandon', ' Doyle'))
            self.assertEqual(self.add({1: 'one'}, {2: 'two'}), self.complete_add({1: 'one'}, {2: 'two'}))
            self.assertEqual(self.add({1}, {2}), self.complete_add({1}, {2}))
    
    
    if __name__ == '__main__':
        main()
    

    I get the following AttributeError:

    
    Traceback (most recent call last):
      File "./test_overloading.py", line 9, in <module>
        class TestOverloading(TestCase):
      File "./test_overloading.py", line 21, in TestOverloading
        def add(x: str, y: str) -> str:
      File "/usr/local/lib/python3.6/site-packages/overloading-0.5.0-py3.6.egg/overloading.py", line 63, in overload
        return register(__registry[fname], func)
      File "/usr/local/lib/python3.6/site-packages/overloading-0.5.0-py3.6.egg/overloading.py", line 207, in register
        dup_sig = sig_cmp(signature, fninfo.signature)
      File "/usr/local/lib/python3.6/site-packages/overloading-0.5.0-py3.6.egg/overloading.py", line 669, in sig_cmp
        match = type_cmp(t1, t2)
      File "/usr/local/lib/python3.6/site-packages/overloading-0.5.0-py3.6.egg/overloading.py", line 702, in type_cmp
        if isinstance(t1, typing.UnionMeta) and isinstance(t2, typing.UnionMeta):
    AttributeError: module 'typing' has no attribute 'UnionMeta'
    
    Reply
  • TypeError: Unions cannot be used with issubclass()
    TypeError: Unions cannot be used with issubclass()

    Mar 18, 2017

    While running the previous test case I've included in #5, I also came across problems in PyCharm where the builtin issubclass is called with arguments type_ and typing.Union (e.g. L#457, L#475, etc.). By the following example, and the source in the standard library, you cannot call issubclass on typing.Union, since it can't be subclassed.

    >>> from typing import Union
    >>> issubclass(str, Union[str])
    True
    >>> issubclass(str, Union[str, float])
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/usr/local/lib/python3.6/typing.py", line 770, in __subclasscheck__
        raise TypeError("Unions cannot be used with issubclass().")
    TypeError: Unions cannot be used with issubclass().
    

    The former only works because the constructor of typing.Union returns the single type (c.f. this comment in the docstring, and this if-statement in the __new__ block).

    Reply
  • Failed to overload instance method in Python 3.6
    Failed to overload instance method in Python 3.6

    Oct 9, 2017

    EDIT: Just realized your test suite doesn't cover Python 3.6.X.

    I don't have this issue when using Python 3.4.3, but when using Python 3.6.3 I receive OverloadErrors when providing overrides for class instance methods.

    Here's a case that gives me an error in the newer version of Python:

    Python 3.6.3 (default, Oct  5 2017, 19:38:03) 
    [GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.72)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from overloading import overload
    >>> class Test:
    ...     @overload
    ...     def m(self, n : int):
    ...         return n + 1
    ...     @overload
    ...     def m(self, n1 : int, n2 : int):
    ...         return n1 + n2
    ... 
    Traceback (most recent call last):
      File "/usr/local/lib/python3.6/site-packages/overloading.py", line 63, in overload
        return register(__registry[fname], func)
    KeyError: '__main__.Test.m'
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 3, in Test
      File "/usr/local/lib/python3.6/site-packages/overloading.py", line 65, in overload
        __registry[fname] = overloaded(func)
      File "/usr/local/lib/python3.6/site-packages/overloading.py", line 151, in overloaded
        return register(dispatcher, func)
      File "/usr/local/lib/python3.6/site-packages/overloading.py", line 205, in register
        .format(dp.__name__, signature.parameters[i]))
    overloading.OverloadingError: Failed to overload function 'm': parameter 'self' has an annotation that is not a type.
    

    What's going on?

    Reply
  • AttributeError: module 'typing' has no attribute 'TypingMeta'
    AttributeError: module 'typing' has no attribute 'TypingMeta'

    Apr 27, 2019

    for some reason my program throws this error rooted in this lib. when i try to simply overload the init of my class. Code:

    from overloading import overload
    import cmath
    
    class Quaternion:
        def __init__(self, r: int, i: int, j: int, k: int):
            self.r = r
            self.i = i
            self.j = j
            self.k = k
    
        @overload
        def __init__(self, c: complex):
            self.r = c.real
            self.i = c.imag
    
    
    print(Quaternion(1, 2, 3, 4).r)
    print(Quaternion(1 + 2j).r)
    

    Error:

    Traceback (most recent call last): File "C:...\Python\Python37\lib\site-packages\overloading.py", line 63, in overload return register(__registry[fname], func) KeyError: 'main.Quaternion.init'

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last): File "C:...\Python\Testing\quaternion.py", line 6, in class Quaternion: File "C:...\Python\Testing\quaternion.py", line 14, in Quaternion def init(self, c: complex): File "C:...\Python\Python37\lib\site-packages\overloading.py", line 65, in overload registry[fname] = overloaded(func) File "C:...\Python\Python37\lib\site-packages\overloading.py", line 151, in overloaded return register(dispatcher, func) File "C:...\Python\Python37\lib\site-packages\overloading.py", line 199, in register signature = get_signature(fn) File "C:...\Python\Python37\lib\site-packages\overloading.py", line 441, in get_signature types = tuple(normalize_type(type_hints.get(param, AnyType)) for param in parameters) File "C:...\Python\Python37\lib\site-packages\overloading.py", line 441, in types = tuple(normalize_type(type_hints.get(param, AnyType)) for param in parameters) File "C:...\Python\Python37\lib\site-packages\overloading.py", line 468, in normalize_type if not typing or not isinstance(type, typing.TypingMeta) or type is AnyType: AttributeError: module 'typing' has no attribute 'TypingMeta'

    Reply
  • Is this still in development, or has is it an abandoned project?
    Is this still in development, or has is it an abandoned project?

    Dec 12, 2020

                                                                                                                                                                                                           
    Reply
  • fixed typing glitch, reorganized structure
    fixed typing glitch, reorganized structure

    Feb 17, 2021

    Isn't compatible with typing anymore, but I used a more modular approach for it to be easier to maintain.

    However, I renamed the module to OverloadingFixed, so please rename it to overloading

    Reply
  • Doesn't work
    Doesn't work

    Oct 16, 2016

    This doesn't seem to work?

    Running your examples:

    @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])
    

    Returns error

    <ipython-input-3-4c7948311942> in biggest(items)
          5 @overload
          6 def biggest(items: Iterable[str]):
    ----> 7     return max(items, key=len)
    
    TypeError: object of type 'int' has no len()
    

    Or with my own example:

    @overload
    def f(x: int):
      return x - 100
    
    @overload
    def f(x: str):
      return "Hello %s" %s
    
    print(f(3))
    print(f("You"))
    
    Traceback (most recent call last):
      File "testoverload.py", line 12, in <module>
        print(f(3))
      File "overload.py", line 184, in f
        raise TypeError('invalid call argument(s)')
    TypeError: invalid call argument(s)
    

    I'm using python3.5

    Reply