amirhossein
amirhossein

Reputation: 39

Abstract method with single-dispatch generic functions

I want to put single-dispatch method in functools module with abstract method in abc module in one method in my abstract class and then using the method in my subclasses based on first argument type. but the problem is the dispatch method doesn't works.

class Abstract(metaclass=ABCMeta):
    ...
    ...
    ...

    @singledispatchmethod 
    @abstractmethod
    def apply(self, element:str):
        raise NotImplementedError

   @apply.register
   @abstractmethod
   def _(self, element: int):
       raise NotImplementedError


class A(Abstract):
    def apply(self, element: str):
        print(f'this is string -> {element}')

    def _(self, element: int):
        print(f'this is intiger -> {element}')


>>>instance = A()
>>>instance.apply(2)
#will return (this is string -> 2)

i have solved this problem in other way but i'm curious about this problem if it has an answer

Upvotes: 2

Views: 1172

Answers (1)

Michael Chen
Michael Chen

Reputation: 645

Implementations

I was curious to find this out, because I just worked on a project where I wanted to have this feature, because I was porting an app from C# where I could simply use overloads. I got this behaviour to work with two different approaches:

Derived class dispatch

This is the version that I needed, which makes the dispatchmethod abstract and every derived class thus also has to define a concrete dispatchmethod with the same signature (important: these are now separate dispatch methods and can provide different registrations). Also note that, because these are concrete, every derived class has their own dispatch and the function in the DoerBase is never called (as it is only abstract).

from abc import ABC, abstractmethod
from functools import singledispatchmethod
from typing import Any
from typing_extensions import override


class DoerBase(ABC):
    @singledispatchmethod
    @abstractmethod
    def do_something(self, arg: Any) -> None: ...

class IntDoer(DoerBase):
    @singledispatchmethod
    @override
    def do_something(self, arg: Any) -> None:
        raise NotImplementedError(f"This {type(self).__name__} cannot do anything with a {type(arg).__name__}!")
    @do_something.register
    def _(self, arg: int):
        print("The number", arg, "is half of", 2 * arg)

class StringDoer(DoerBase):
    @singledispatchmethod
    @override
    def do_something(self, arg: Any) -> None:
        raise NotImplementedError(f"This {type(self).__name__} cannot do anything with a {type(arg).__name__}!")
    @do_something.register
    def _(self, arg: str):
        print("I can print this string for you:", arg)

def main():
    int_doer = IntDoer()
    string_doer = StringDoer()
    int_doer.do_something(321)
    string_doer.do_something("Hello")
    # This IntDoer cannot do anything with a str!
    # int_doer.do_something("Hello")
    # This StringDoer cannot do anything with a int!
    # string_doer.do_something(321)

if __name__ == "__main__":
    main()

Base class dispatch

The version which is more similar to the one in your example declares the registered dispatch types in the base class (the above method declares registrations per derived class). Now every base class must override the abstract dispatch registrations. I was able to recreate this behaviour by calling an abstract method from the registered dispatch handler instead of trying to make the dispatch handler itself abstract.

from abc import ABCMeta, abstractmethod
from functools import singledispatchmethod
from typing import Any


class ABase(metaclass=ABCMeta):
    @singledispatchmethod
    def apply(self, element: Any) -> None: ...

    @apply.register
    def _(self, element: int): return self.apply_int(element)

    @abstractmethod
    def apply_int(self, element: int) -> None: ...

class A(ABase):
    def apply_int(self, element: int):
        print("I applied the number", element)

class B(ABase): pass

instance = A()
instance.apply(2)
#will print "I applied the number 2"

# b_instance = B()
# TypeError: Can't instantiate abstract class B with abstract method apply_int

Conclusions:

  • On your derived class you definitely also need to provide the @singledispatchmethod decorator
  • It seems that you cannot combine the @method.register decorator with the @abstractmethod decorator
  • You can circumvent this behaviour by careful placement of the @abstractmethod decorator
  • The two above implementations differ in their behaviour: one declares that the class has a dispatch method, that each subclass has to declare and register, while the other implementation defines the dispatch registrations that all subclasses have to fulfill

Upvotes: 0

Related Questions