Pythonic multiple inheritance to generate collections of custom classes

Viewed 136

I've got two class trees in my Python code:

       BaseComponent                      BaseSeries
      /      |      \                          |
Resistor Capacitor Inductor                 ESeries

The BaseSeries class tree implements preferred numbers such as the E-series, and generates sets of values between a pair of powers (e.g. [1.0, 2.2, 4.7, 10, 22, 47, 100, 220, 470] for the E3 series with exponents between 1 to 3).

By default, ESeries and any other instance of BaseSeries creates sequences of float objects. I'd like to use these classes to instead create sequences of Resistor, Capacitor and Inductor objects. Ideally, the individual Resistor, Capacitor, Inductor and ESeries classes would remain usable on their own (i.e. not rely on methods being implemented by other classes).

This sounds like a job for multiple inheritance, but I'm a bit confused about how best to implement this in Python (3). Ideally I'd like to just define something like:

class ResistorESeries(Resistor, ESeries):
    pass

class CapacitorESeries(Capacitor, ESeries):
    pass

class InductorESeries(Inductor, ESeries):
    pass

in order to create classes that yield sequences of resistors, capacitors and inductors, but I don't know how best to tell BaseSeries instances to create objects of type Resistor, Capacitor and Inductor. I can think of two ways, but I can't decide which one is best, and I have a feeling there is a simpler, more Pythonic way that I'm missing:

  1. have BaseSeries contain a property or variable pointing to the element type (e.g. Resistor) set either by the constructor, a class variable in the child class (e.g. Resistor.ELEMENT_TYPE = Resistor) or with an abstract property provided by the child class:

    class BaseSeries(object):
        ...
        def elements(self):
            # loop over numbers in this series
            for v in self.values():
                yield self.element_type(v)
    
        @property
        @abc.abstractmethod
        def element_type(self):
            return NotImplemented
    
    class ESeries(BaseSeries):
        ....
    
    class BaseComponent(object):
        ...
        @property
        def element_type(self):
            return self
    
    class Resistor(BaseComponent):
        ...
    
    class ResistorESeries(Resistor, ESeries):
        # now BaseSeries' `element_type` property is provided by `BaseComponent`
        pass
    

    This would mean ESeries cannot be used on its own as a concrete object, as it does not implement this property/variable, which is not ideal.

  2. use self when creating elements in BaseSeries, where self will, as long as Resistor is earlier in the method resolution order, refer to the desired element:

    class BaseSeries(object):
        ...
        def elements(self):
            # loop over numbers in this series
            for v in self.values():
                # self here would refer to `Resistor` in
                # `ResistorESeries` instances
                yield self(v)
    
    class ESeries(BaseSeries):
        ....
    
    class BaseComponent(object):
        ...
    
    class Resistor(BaseComponent):
        ...
    
    class ResistorESeries(Resistor, ESeries):
        pass
    

This has the downside that, in instances of ESeries without being used as a mix-in, self will refer to itself, which does not support the correct __init__ signature.

So, does anyone have an idea of how best to do this in a Pythonic way, with maximum ability to reuse classes on their own?

1 Answers
Related