The Python Oracle

Method overloading for different argument type in python

This video explains
Method overloading for different argument type in python

--

Become part of the top 3% of the developers by applying to Toptal
https://topt.al/25cXVn

--

Music by Eric Matyas
https://www.soundimage.org
Track title: Over a Mysterious Island

--

Chapters
00:00 Question
02:02 Accepted answer (Score 19)
02:37 Answer 2 (Score 20)
03:40 Answer 3 (Score 14)
05:16 Answer 4 (Score 4)
07:33 Thank you

--

Full question
https://stackoverflow.com/questions/2534...

Question links:
[interesting "visitor" code]: http://curtis.schlak.com/2013/06/20/foll...

Answer 1 links:
[backport]: https://pypi.python.org/pypi/singledispa...
[functools.singledispatch]: https://docs.python.org/3/library/functo...
[this gist.]: https://gist.github.com/ambv/5682351

Answer 2 links:
[Guido van Rossum's multimethod decorator]: http://www.artima.com/weblogs/viewpost.j...

Answer 3 links:
[functools.singledispatch]: https://docs.python.org/3/library/functo...
[PEP-443]: https://www.python.org/dev/peps/pep-0443/
[another answer]: https://stackoverflow.com/a/25344445/389...
[backport]: https://pypi.org/project/singledispatch/
[source code]: https://github.com/python/cpython/blob/4...
[this answer]: https://stackoverflow.com/a/24602374/389...

--

Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...

--

Tags
#python #oop #designpatterns

#avk47



ANSWER 1

Score 22


If you're using Python 3.4 (or are willing to install the backport for Python 2.6+), you can use functools.singledispatch for this*:

from functools import singledispatch

class S_Block(object): pass
class S_Empty(object): pass
class S_Function(object): pass


class Test(object):
    def __init__(self):
        self.render = singledispatch(self.render)
        self.render.register(S_Block, self._render_block)
        self.render.register(S_Empty, self._render_empty)
        self.render.register(S_Function, self._render_function)

    def render(self, s):
        raise TypeError("This type isn't supported: {}".format(type(s)))

    def _render_block(self, s):
        print("render block")

    def _render_empty(self, s):
        print("render empty")

    def _render_function(self, s):
        print("render function")


if __name__ == "__main__":
    t = Test()
    b = S_Block()
    f = S_Function()
    e = S_Empty()
    t.render(b)
    t.render(f)
    t.render(e)

Output:

render block
render function
render empty

*Code based on this gist.




ACCEPTED ANSWER

Score 20


Would something like this work?

self.map = {
            S_Block : self._render_block,
            S_Empty : self._render_empty,
            S_Function: self._render_function
}
def render(self, s):
    return self.map[type(s)](s)

Keeping a reference to a class object as a key in a dictionary and having it's value be the function object you want to call will make your code shorter and less error prone. The only place an error could occur here would be in the definition of the dictionary. Or one of your internal functions of course.




ANSWER 3

Score 15


The overloading syntax you are looking for can be achieved using Guido van Rossum's multimethod decorator.

Here is a variant of the multimethod decorator which can decorate class methods (the original decorates plain functions). I've named the variant multidispatch to disambiguate it from the original:

import functools

def multidispatch(*types):
    def register(function):
        name = function.__name__
        mm = multidispatch.registry.get(name)
        if mm is None:
            @functools.wraps(function)
            def wrapper(self, *args):
                types = tuple(arg.__class__ for arg in args) 
                function = wrapper.typemap.get(types)
                if function is None:
                    raise TypeError("no match")
                return function(self, *args)
            wrapper.typemap = {}
            mm = multidispatch.registry[name] = wrapper
        if types in mm.typemap:
            raise TypeError("duplicate registration")
        mm.typemap[types] = function
        return mm
    return register
multidispatch.registry = {}

and it can be used like this:

class Foo(object):
    @multidispatch(str)
    def render(self, s):
        print('string: {}'.format(s))
    @multidispatch(float)
    def render(self, s):
        print('float: {}'.format(s))
    @multidispatch(float, int)
    def render(self, s, t):
        print('float, int: {}, {}'.format(s, t))

foo = Foo()
foo.render('text')
# string: text
foo.render(1.234)
# float: 1.234
foo.render(1.234, 2)
# float, int: 1.234, 2

The demo code above shows how to overload the Foo.render method based on the types of its arguments.

This code searches for exact matching types as opposed to checking for isinstance relationships. It could be modified to handle that (at the expense of making the lookups O(n) instead of O(1)) but since it sounds like you don't need this anyway, I'll leave the code in this simpler form.




ANSWER 4

Score 3


To add some performance measurements to the @unutbu 's answer:

@multimethod(float)
def foo(bar: float) -> str:
    return 'float: {}'.format(bar)

def foo_simple(bar):
    return 'string: {}'.format(bar)

import time

string_type = "test"
iterations = 10000000

start_time1 = time.time()
for i in range(iterations):
    foo(string_type)
end_time1 = time.time() - start_time1


start_time2 = time.time()
for i in range(iterations):
    foo_simple(string_type)
end_time2 = time.time() - start_time2

print("multimethod: " + str(end_time1))
print("standard: " + str(end_time2))

Returns:

> multimethod: 16.846999883651733
> standard:     4.509999990463257