0

I am given a Python base class that defines an interface which I must implement in a derived class. In the base class, each method is defined as a stub that throws NotImplementedError.

There are many such methods and their names are mostly very regular, of the form <Operation><Type>. In principle the vast majority could be implemented by dispatching to a single method that takes "<Operation>" and "<Type>", parsed from the method name, as arguments. I would like to do something like this to avoid boilerplate. However, there are a small number of methods that would need an explicit override.

class Base(object):
    ...
    # 'Regular' methods - the majority of the interface
    def CreateObj1(self, arg):
        raise NotImplementedError()
    def DeleteObj1(self, arg):
        raise NotImplementedError()
    # etc.. for a number of such operations
    #       for many type Obj1, Obj2, ...

    # A few methods that don't fit the above pattern
    def SpecialMethod1(self, arg):
        raise NotImplementedError()
    ...

class Derived(Base):
    ...
    def _impl(self, operation_name, object_type, arg):
        # With this I am essentially able to implement
        # all of the 'regular' methods
        ...

    def CreateObj1(self, arg):
        return self._impl('Create', 'Obj1', arg)
    def DeleteObj1(self, arg):
        return self._impl('Delete', 'Obj1', arg)

    def SpecialMethod1(self, arg):
        # This still needs an explicit implementation
        ...
    ... 

I could just implement it as above but I would prefer not to have to write all of the explicit delegators for the 'regular' methods as it adds a place that needs maintenance when the interface is extended.

My first thought was to define a __getattr__ method, but the problem is that the base stub methods 'win' and __getattr__ is not called. I think I might be able to make it work with __getattribute__ but I have never needed to use this before and am wary because I understand that one can easily get into a mess by misusing it.

Does __getattribute__ sound like the correct way to go with this or are there other approaches that I might be missing?

If it is the right approach, are there any particular patterns I could follow to ensure that I use it correctly?

What would be the best way to deal with the small number of methods that cannot be dynamically overridden and which need explicit implementations in the derived class?

8
  • Why don't you just have a single method that takes operation and type as parameters? Commented Jun 15, 2021 at 22:15
  • You should make the base class an Abstract Base Class, that will automate the "not implemented" errors. Commented Jun 15, 2021 at 22:32
  • @Barmar the base class is not under my control. Commented Jun 15, 2021 at 22:44
  • Edited wording slightly to make this clearer. Commented Jun 16, 2021 at 6:58
  • It's hard to answer questions in abstract like this. Please show the actual code you're talking about. Commented Jun 17, 2021 at 13:55

1 Answer 1

1

You can use a metaclass (handle with care)

class BaseMeta(type):
    def get_default_impl(base_attr, subject="Obj1"):
        # modify this method
        action, _ = base_attr.split(subject)

        def _default(self, arg):
            return self._impl(action, subject, arg)
        return _default

    def __new__(mcs, name, bases, attrs):
        # print("attrs:", attrs, "base:", bases)
        for base in bases:
            for base_attr in dir(base):
                if base_attr.endswith("Obj1"):  # modify this
                    attrs.setdefault(base_attr, mcs.get_default_impl(base_attr))

        def _impl(self, operation_name, object_type, arg):
            raise NotImplementedError()

        attrs.setdefault("_impl", _impl)
        return super().__new__(mcs, name, bases, attrs)


class Derived(Base, metaclass=BaseMeta):
    def _impl(self, operation_name, object_type, arg):
        print("got op:{}, obj: {}, arg: {}".format(operation_name, object_type, arg))

You will need to modify if base_attr.endswith("Obj1"): and get_default_impl according to how the methods on the base class are defined. I've worked with the example you've posted.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.