2

This question is specific for python 3. Suppose I have a class hierarchy like this

class Base():
   def calculate():
       return 0

class Derived1(Base):
     def calculate():
        # some calculation

class Derived2(Base):
     def calculate():
        # some calculation

Now, what I want to do is make a class that defines a generic way to inherit from the Derived classes, and then overrides calculate. In other words, something in the spirit of C++ templates, to avoid copying over the subclasses code, but specify a generic way of subclassing, and then be able to define the subclasses as one liners, like shown below:

# pseudocode
class GenericDerived5(someGenericBase):
      def calculate():
          return super().calculate() + 5

class GenericDerived6(someGenericBase):
      def calculate():
          return super().calculate() + 5

class Derived5_1 = GenericDerived5(Derived1)
class Derived6_1 = GenericDerived6(Derived2)

(the calculation is not literally like this, just illustrating the combinatorial nature of the inheritance structure) How would this code look like, and what are the relevant tools from python3 that I need? I've heard of metaclasses, but not very familiar.

3
  • Can you clarify what you are trying to do? You obviously know about class definitions and inheritance already, why don't you solve the problem using this tool? class Derived5_1(GenericDerived5, Derived1) should do what you want, and is almost verbatim in the question. Commented Jul 4, 2020 at 13:10
  • What I said in the question: "generic way to inherit from the Derived classes". Instead of explicitly specifying all combinations of base class and derived, define a generic way of inheritance, perhaps using type, and then just define the classes as one-liners. Commented Jul 4, 2020 at 14:24
  • I don't follow how your requirements conflict with mixins. Multiple inheritance is also a single line of code. As mentioned, it is almost verbatim the same as the pseudocode. Can you please clarify what else you need, or how specifically mixins conflict with what you want? Commented Jul 4, 2020 at 16:13

2 Answers 2

2

class definition inside a factory-function body

The most straightforward way to go there is really straightforward - but can feel a bit awkward:

def derived_5_factory(Base):
    class GenericDerived5(Base):
        def calculate(self):
            return super().calculate() + 5
    return GenericDerived5

def derived_6_factory(Base):
    class GenericDerived6(Base):
        def calculate(self):
            return super().calculate() + 6
    return GenericDerived6

Derived5_1 = derived_5_factory(Derived1)
Derived6_2 = derived_6_factory(Derived2)

The inconvenient part is that your classes that need generic bases have to be defined inside function bodies. That way, Python re-executes the class statement itself, with a different Base, taking advantage that in Python classes are first class objects.

This code have the inconveniences that (1) the class bodies must be inside functions, and (2) it can be the wrong approach at all:

Multiple inheritance

If you can have an extra inheritance level - that is the only difference for your example, this is the "correct" way to go. Actually, apart from having the former "GenericDerived" classes explicitly in their inheritance chain, they will behave exactly as intended:


class Base():
   def calculate():
       return 0

class Derived1(Base):
     def calculate(self):
        return 1

class Derived2(Base):
     def calculate(self):
        return 2

#  mix-in bases:

class MixinDerived5(Base):
    def calculate(self):
        return super().calculate() + 5

class MixinDerived6(Base):
    def calculate(self):
        return super().calculate() + 6


Derived5_1 = type("Derived5_1", (MixinDerived5, Derived1), {})
Derived6_2 = type("Derived6_2", (MixinDerived6, Derived2), {})

Here, instead of using the class statement, a dynamic class is created with the type call, using both the class that needs a dybamic base and that dynamic base as its bases parameter. That is it - Derived5_1 is a fully working Python class with both Bases in its inheritance chain

Note that Python's super() will do exactly what common sense would expect it to do, "rerouting" itself through the extra intermediary "derived" classes before reaching "Base". So, this is what I get on the interactive console after pasting the code above:

In [6]: Derived5_1().calculate()                                                                 
Out[6]: 6

In [7]: Derived6_2().calculate()                                                                 
Out[7]: 8
Sign up to request clarification or add additional context in comments.

7 Comments

I'm not sure it's worth dragging an explicit call to type into the answer; class Derived5_1(MixinDerived5, Derived1): ... works just as well.
The problem with the factory-function approach is that you are actually creating separate GenericDerived* classes every time you call it, meaning two classes that look like they derive from the same class really haven't.
While type usually does the correct thing, types.new_class always does.
Type seems to be in the spirit of the question, as I am looking for something analogous to C++ templates, to some extend. Will look into this further. Please reread the question, added some details there.
The factory function should add a cache mechanism, to avoid re-creating classes in each call. However, if you really want ONE class that behaves like it derives from a different base, depending on how it is called - that is tricky - it could be done by some mechanism that should be used in place of super() and could be put on a metaclass.
|
1

A mix-in class, roughly speaking, is a class that isn't intended to be instantiated directly or act as a standalone base class (other than for other, more specialized mix-in classes), but to provide a small subset of functionality that another class can inherit.

In this case, your GenericDerived classes are perfect examples of mix-ins: you aren't creating instances of GenericDerived, but you can inherit from them to add a calculate method to your own class.

class Calculator:
    def calculate(self):
        return 9

class Calculator1(Calculator):
      def calculate(self):
          return super().calculate() + 5

class Calculator2(Calculator):
      def calculate(self):
          return super().calculate() + 10

class Base(Calculator):
    ...

Note that the Base and Calculator hierarchies are independent of each other. Base provides, in addition to whatever else it does, basic calculate functionality. A subclass of Base can use calculate that it inherits from Base (via Calculator), or it can inherit from a subclass of Calculator as well.

class Derived1(Base):
    ...

class Derived2(Base, Calculator1):
    ...

class Derived3(Base, Calculator2):
    ...

1 Comment

I know about mixin. It is not what I want, not related to the question. This is more in the spirit of C++ templates. Will clarify the question.

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.