Karolis JuodelÄ— Karolis JuodelÄ— - 27 days ago 6
Python Question

How to define three methods circularly?

I have an abstract class with three methods that are is a sense equivalent - they could all be defined in terms of each other using some expensive conversion functions. I want to be able to write a derived class which would only need to override one of the methods and automatically get the other two. Example

class FooBarBaz(object):
def foo(self, x):
return foo_from_bar(self.bar(x))
# OR return foo_from_baz(self.baz(x))

def bar(self, x):
return bar_from_foo(self.foo(x))
# OR return bar_from_baz(self.baz(x))

def baz(self, x):
return baz_from_bar(self.bar(x))
# OR return baz_from_foo(self.foo(x))

class Derived1(FooBarBaz):
def bar(self, x):
return 5
# at this point foo = foo_from_bar(5) and
# baz = baz_from_bar(5), which is what I wanted

class Derived2(FooBarBaz):
def foo(self, x):
return 6
# at this point bar = bar_from_foo(6) and
# baz = baz_from_bar(bar_from_foo(6)),
# which is not ideal, but still works

class Derived3(FooBarBaz):
def baz(self, x):
return 7
# at this point foo and bar remain defined
# in terms of each other, which is a PROBLEM


I know I could explicitly tell each derived class which conversions to use. I want to know if there is a way for the parent class to figure this out on its own, without modifying the children.

Answer

You could resort to metaprogramming techniques like writing a metaclass that fills in the remaining methods automatically, or use introspection to look at the classes in type(self).mro() in turn to find out which methods have been overridden. However, these options firmly fall into the "too much magic" category for me, so I'd go with something simpler.

Simply split each method up into two: One generic one, and the actual implementation. Derived classes override the actual implementation:

class FooBarBaz(object):

    def foo_impl(self, x):
        raise NotImplementedError

    def foo(self, x):
        try:
            return self.foo_impl(x)
        except NotImplementedError:
            try:
                return foo_from_bar(self.bar_impl(x))
            except NotImplementedError:
                return foo_from_baz(self.baz_impl(x))

    # Similarly fo bar and baz

class Dervied(FooBarBaz):

    def bar_impl(self, x):
        return 5

The common logic can also be factored out in a decorator:

def first_implemented(func):
    @functools.wraps
    def wrapper(*args, **kwargs):
        for f in func(*args, **kwargs):
            try:
                return f()
            except NotImplementedError:
                pass
        raise NotImplementedError
    return wrapper

class FooBarBaz(object):

    def foo_impl(self, x):
        raise NotImplementedError

    @first_implemented
    def foo(self, x):
        yield lambda: self.foo_impl(x)
        yield lambda: foo_from_bar(self.bar_impl(x))
        yield lambda: foo_from_baz(self.baz_impl(x))