1

I'm making the transition over to Python3 and have been exploring some of the functionality of the stdlib. functools.singledispatch caught my eye and I've been playing around with it a little bit. However, at the point where I tried using it in a class I ran into some problems.

It doesn't appear to work with functions registered inside the class, you can make it work by directly calling fun.dispatch(type(arg))(argname=arg) and I was wondering if there was a better way to do it.

I tried using @classmethod and @staticmethod as decorators above and below the registration but that didn't work.

Here's a contrived example that registers handlers to convert the input argument when creating a class to ensure that it will always be a list.

from functools import singledispatch

class UrlDispatcher(object):

    @singledispatch
    def url_input(self, input):
        print('input wasn\'t dispatched', input)

    @url_input.register(str)
    def _(self, input):
        print('input is a str', input)
        self.input = [input]

    @url_input.register(list)
    def _(self, input):
        print('input is a list', input)
        self.input = input

    def __init__(self, arg):

        # Works, albeit clunkily
        self.url_input.dispatch(type(arg))(self,input=arg)

        # Always uses the base dispatcher
        self.url_input(input=arg)

a = "http://www.cnn.com"
b = ["http://www.google.com", "http://www.slashdot.org"]

s1 = UrlDispatcher(a)
s2 = UrlDispatcher(b)
Charles
  • 50,943
  • 13
  • 104
  • 142
synthesizerpatel
  • 27,321
  • 5
  • 74
  • 91
  • Well, the dispatch happens on the type of the first argument...which is `self` – Gerrat Nov 29 '13 at 03:40
  • Yeah, I get why it doesn't work - I'm interested in how it could work. – synthesizerpatel Nov 29 '13 at 04:08
  • `x.y(z)` already performs single dispatch on the type of `x`. What you're trying to get is multiple dispatch. It makes sense that `functools.singledispatch` might not provide a natural solution. – user2357112 Nov 29 '13 at 04:26

4 Answers4

3

The following should work. Whether or not it's the best solution, I don't know.

class Foo:
    def method(self, arg):
        _method(arg, self)

@functools.singledispatch
def _method(arg, self):
    ...

...
...
user2357112
  • 260,549
  • 28
  • 431
  • 505
2

https://docs.python.org/3/library/functools.html#functools.singledispatchmethod

as of python 3.8 there is an stdlib function for method dispatching

Max
  • 907
  • 2
  • 13
  • 27
1

I found the answer - you don't.

http://code.activestate.com/lists/python-dev/122554/

Quoting from a post I found at the above URL I think it's explained - short answer is 'generic functions' are for stateless algorithms. I was unaware of that definition.

Correct. OO and generic functions are different development paradigms, and there are limitations on mixing them. Generic functions are for stateless algorithms, which expect to receive all required input through their arguments. By contrast, class and instance methods expect to receive some state implicitly - in many respects, they already are generic functions.

Thus, this is really a request for dual dispatch in disguise: you want to first dispatch on the class or instance (through method dispatch) and then dispatch on the second argument (through generic function dispatch).

Dual dispatch is much harder than single dispatch and "functools.singledispatch" does not and should not support it (it's in the name). As PJE noted, you can use singledispatch with staticmethods, as that eliminates the dual dispatch behaviour by removing the class and instance based dispatch step. You can also register already bound class and instance methods as implementations for a generic function, as that also resolves the dual dispatch in a way that means the single dispatch implementation doesn't even need to be aware it is happening.

synthesizerpatel
  • 27,321
  • 5
  • 74
  • 91
1

Here is my solution, work on any Python (3.8 or less)

class MyClass:  

    def over_ride_func( self, arg):
        if type(arg) is list:
            self.over_ride_func_list (arg)
            
        if type(arg) is int:
            self.over_ride_func_int (arg)       

        if type(arg) is float:
            self.over_ride_func_float (arg)     

        if type(arg) is str:
            self.over_ride_func_string (arg)        
            
    def over_ride_func_list(self, arg ):
        print ("List arg: ", arg, " with legnth", len(arg), " first item", arg[0])

    def over_ride_func_int(self, arg ):
        print ("int arg: ", arg)

    def over_ride_func_float(self, arg ):
        print ("Float arg: ", arg)

    def over_ride_func_string(self, arg ):
        print ("String arg ", arg)


obj_myclass = MyClass()
obj_myclass.over_ride_func(665)
obj_myclass.over_ride_func(444.31)
obj_myclass.over_ride_func([3,5,6])
obj_myclass.over_ride_func("Hello over ride function")
aymhenry
  • 81
  • 4