How can I use functools.singledispatch with instance methods?

Python 3.4 added the ability to define function overloads using static methods. This is essentially an example from the documentation:

from functools import singledispatch class TestClass(object): @singledispatch def test_method(arg, verbose=False): if verbose: print("Let me just say,", end=" ") print(arg) @test_method.register(int) def _(arg): print("Strength in numbers, eh?", end=" ") print(arg) @test_method.register(list) def _(arg): print("Enumerate this:") for i, elem in enumerate(arg): print(i, elem) if __name__ == '__main__': TestClass.test_method(55555) TestClass.test_method([33, 22, 11]) 

In its purest form, the implementation of singledispatch based on the first argument for identifying the type, making it difficult to extend this function to instance methods.

Does anyone have any advice on how to use (or jerry-rig) this function to make it work with instance methods?

+40
python single-dispatch
Jul 07 '14 at 0:50
source share
3 answers

Update: As of Python 3.8, functools.singledispatchmethod will allow a single dispatch by methods, class methods, abstractmethods, and staticmethods.

For older versions of Python, see the rest of this answer.

Looking at the source for singledispatch , we see that the decorator returns a wrapper() function that selects the function to call from the registered ones, based on the type args[0] ...

  def wrapper(*args, **kw): return dispatch(args[0].__class__)(*args, **kw) 

... which is good for a regular function, but not very useful for an instance method, whose first argument will always be self .

We can, however, write a new decorator methdispatch that relies on singledispatch to do the hard work, but instead returns a wrapper function that selects which registered function to call based on the args[1] type args[1] :

 from functools import singledispatch, update_wrapper def methdispatch(func): dispatcher = singledispatch(func) def wrapper(*args, **kw): return dispatcher.dispatch(args[1].__class__)(*args, **kw) wrapper.register = dispatcher.register update_wrapper(wrapper, func) return wrapper 

Here is a simple example of using a decorator:

 class Patchwork(object): def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) @methdispatch def get(self, arg): return getattr(self, arg, None) @get.register(list) def _(self, arg): return [self.get(x) for x in arg] 

Note that both the decorated get() method and the method registered in list , as usual, have an initial argument of self .

Testing the Patchwork class:

 >>> pw = Patchwork(a=1, b=2, c=3) >>> pw.get("b") 2 >>> pw.get(["a", "c"]) [1, 3] 
+62
Jul 07 '14 at 2:37
source share

A decorator is essentially a wrapper that takes a packed function as an argument and returns another function.

As indicated in the accepted answer, singledispatch returns a wrapper that takes the first argument as a registered type - self in instance methods.

As shown in this answer, in such cases, you can write another wrapper so that the monkey fixes the decorator. But such hacker fixes are not always the best option.

As with any other function, you can call the wrapper and pass arguments to it explicitly, which seems easier, clearer, and easier to read if this type of method is rarely overloaded in a package.

 from functools import singledispatch class TestClass(object): def __init__(self): self.test_method = singledispatch(self.test_method) self.test_method.register(int, self._test_method_int) self.test_method.register(list, self._test_method_list) def test_method(self, arg, verbose=False): if verbose: print("Let me just say,", end=" ") print(arg) def _test_method_int(self, arg): print("Strength in numbers, eh?", end=" ") print(arg) def _test_method_list(self, arg): print("Enumerate this:") for i, elem in enumerate(arg): print(i, elem) if __name__ == '__main__': test = TestClass() test.test_method(55555) test.test_method([33, 22, 11]) 

There is another module, multipledispatch (not standard, but included in Anaconda and without any non-standard dependencies), which, as the name implies and unlike singledispatch , allows multimethods.

In addition to the Dispatcher objects with singledispatch -compatible, it provides a dispatch decorator that hides the creation and management of these objects from the user.

The dispatch decorator uses the function name to select the appropriate Dispatcher object to which it adds a new signature / function. When it encounters a new function name, it creates a new Dispatcher object and saves the name / dispatcher pair in the namespace for future use.

For example:

 from types import LambdaType from multipledispatch import dispatch class TestClass(object): @dispatch(object) def test_method(self, arg, verbose=False): if verbose: print("Let me just say,", end=" ") print(arg) @dispatch(int, float) def test_method(self, arg, arg2): print("Strength in numbers, eh?", end=" ") print(arg + arg2) @dispatch((list, tuple), LambdaType, type) def test_method(self, arg, arg2, arg3): print("Enumerate this:") for i, elem in enumerate(arg): print(i, arg3(arg2(elem))) if __name__ == '__main__': test = TestClass() test.test_method(55555, 9.5) test.test_method([33, 22, 11], lambda x: x*2, float) 
+11
Aug 28 '17 at 10:27
source share
0
Feb 06 '19 at 3:58
source share



All Articles