Search code examples
pythonpolymorphismlate-bindingdynamic-dispatch

Referring to a pure virtual method


I am probably missing something obvious here. I have a simple class hierarchy that looks roughly like this:

class Any:
    def op2_additive_add(self, other: 'Any') -> 'Any':
        raise NotImplementedError

    def op2_multiplicative_multiply(self, other: 'Any') -> 'Any':
        raise NotImplementedError

    def op2_exponential_power(self, other: 'Any') -> 'Any':
        raise NotImplementedError

    # A dozen of similar methods not shown

class Rational(Any):
    def op2_additive_add(self, other: 'Any') -> 'Rational':
        pass    # Implementation not shown

    def op2_multiplicative_multiply(self, other: 'Any') -> 'Rational':
        pass    # Implementation not shown

    def op2_exponential_power(self, other: 'Any') -> 'Rational':
        pass    # Implementation not shown

class Set(Any):
    def op2_additive_add(self, other: 'Any') -> 'Set':
        pass    # Implementation not shown

    def op2_multiplicative_multiply(self, other: 'Any') -> 'Set':
        pass    # Implementation not shown

    def op2_exponential_power(self, other: 'Any') -> 'Set':
        pass    # Implementation not shown

# Half a dozen of similar derived types not shown.

I am implementing a dispatcher class that is supposed to choose the requested operation on two operands while not knowing their types, and pass a reference to the correct method onwards to an executor. Roughly like this (the code below will not work):

def select_operator(selector) -> typing.Callable[[Any, Any], Any]:
    if selector.such_and_such():
        return Any.op2_additive_add
    elif selector.so_and_so():
        return Any.op2_exponential_power
    # And so on

The above code will not work because an attempt to invoke a returned unbound method will bypass the dynamic dispatch; i.e. select_operator(selector)(foo, bar) will always throw a NotImplementedError.

The best solution I could come up with so far is roughly like this, and it is not pretty:

def select_operator(selector) -> str:
    if selector.such_and_such():
        return Any.op2_additive_add.__name__
    elif selector.so_and_so():
        return Any.op2_exponential_power.__name__
    # And so on

method_name = select_operator(selector)
getattr(foo, method_name)(bar)

TL;DR: How do I delay the dynamic dispatch process until after I took a reference to a method?


Solution

  • Maybe this will be more suitable?

    class Any:
        def select_and_do_op(self, selector, other):
            if selector.such_and_such():
                return self.op2_additive_add.(other)
            elif selector.so_and_so():
                return self.op2_exponential_power(other)
        ...
    
    foo.select_and_do_op(selector, bar)
    

    UPDATE

    One another solution:

    def select_operator(selector):
        if selector.such_and_such():
            return lambda foo, bar: foo.op2_additive_add(bar)
        elif selector.so_and_so():
            return lambda foo, bar: foo.op2_exponential_power(bar)
        ...
    
    operator = select_operator(selector)
    result = operator(foo, bar)