1

I have two (non-trivial) classes like this:

class MyClass(Base):
    def __init__(self, etc, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.etc = etc

    def do_something(self, arg1, arg2):
        # not actually a function call, just to make this as minimal as possible
        output = computation_with_a_bunch_of_code(arg1, arg2)
        return super().do_something(output)

Don't get too attached to the exact format, the super call may be in the really long computation (possibly multiple times), but the only difference is the super delegated to.

I've tried looking at related questions and none quite did what I wanted. For instance, This recommended one with multiple inheritance doesn't rely on super calls the way mine does.

While I'd love to just instantiate base like an object like so:

class MyFakeBaseClass:
    def __init__(self, etc, base):
        self.base = base
        self.etc = etc

    def do_something(self, arg1, arg2):
        output = computation_with_a_bunch_of_code(arg1, arg2)
        return self.base.do_something(output)

The library I'm using requires the ultimate base class of whatever I pass in to be one of its base classes (I think it uses metaclass subtyping magic to do things to your overridden methods, because duck typing doesn't generally work). While I could use the API's lowest level abstract class as my base, I'd have to do a lot of legwork to manually override a ton of methods with self.base.method() which would end up being about as much boilerplate code as normal code I'm replacing anyway, and a nightmare to read.

My ideal solution would look something like:

class _BaseClass(???):
    def __init__(self, etc, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.etc = etc
    
    def do_something(self, arguments):
        arguments = computation_with_a_bunch_of_code()
        return super().do_something(arguments)

class RealOne(_BaseClass(SuperOne)): pass

class RealTwo(_BaseClass(SuperTwo)): pass  

I've tried typing.Generic since on the surface it seemed similar from examples, but given a Generic[T], it doesn't seem to use T as a real concrete super class when calling super. And on further research, it appears you can't really use it this way anyway, due to when the relevant values are set if nothing else.

Currently I've settled on:

def _actually_do_something(base, obj, arg1, arg2):
    output = computation_with_a_bunch_of_code(arg1, arg2)
    return base.do_something(output)

class MyClass(Base):
    # ... etc ...
    def do_something(self, arg1, arg2):
        base = super()
        return _actually_do_something(base, self, arg1, arg2)

Which is fine, but with multiple methods doing this it's a lot of boilerplate, and a frustrating amount of indirection, somewhat to me, but definitely to other people working on this who I've shown it to. It's not completely unmaintainable, but if one exists I'd like a cleaner solution.

It seems to me that the only other solution within my (unfortunate) constraints is metaclasses, but I couldn't find anything that quite did what I wanted. Most examples were either trivial (setting an attribute), or didn't specify how to use a dynamic base class. I'd welcome a simpler solution even more, but as far as I can tell decorators and the like can't quite do this. PEP 638 (Syntactic Macros) seems like it'd be an easy avenue to what I want, but as far as I can tell it's not implemented yet, and I'm unfortunately tied to environments that don't have 3.9 available yet anyway if it is (I'm stuck on 3.6, specifically). It's possible the answer is "just do what you're doing now and update to that whenever it comes out," but again, if there's something relatively simple, compact, and nice looking I'm missing I'd like to know.

Linear
  • 21,074
  • 4
  • 59
  • 70

0 Answers0