synthesizerpatel
synthesizerpatel

Reputation: 28036

Pythonic way of using Python3's functools.singledispatch in a class?

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)

Upvotes: 1

Views: 3171

Answers (4)

aymhenry
aymhenry

Reputation: 91

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")

Upvotes: 1

Max
Max

Reputation: 1175

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

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

Upvotes: 2

synthesizerpatel
synthesizerpatel

Reputation: 28036

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.

Upvotes: 1

user2357112
user2357112

Reputation: 281683

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

...
...

Upvotes: 3

Related Questions