Search code examples
pythoninheritancetype-conversionmultiple-inheritance

Python: Constructing a multiple-inheritance subclass instance from a superclass


So I have in my project two classes: Circuit and SubCircuit. At some point I may need to construct a SubCircuit from a Circuit. Is there a more elegant way to do that than what's done below in the last 4 lines? Circuit may get some new attributes at some point for example, which would mean that conversion would also need to be updated - basically inviting bugs.

class Gate(abc.ABC):
    def __init__(self, size):
        self.in_ports = (InPort(self),) * size
        self.out_ports = (OutPort(self),) * size


class Circuit:
    def __init__(self, size):
        self.input = (OutPort(self),) * size
        self.output = (InPort(self),) * size
        self.gates = []

    # ... some other stuff that messes around with these attributes


class SubCircuit(Gate, Circuit):
    def __init__(self, circuit=None):
        Gate.__init__(self, size)
        Circuit.__init__(self, size)

        if circuit is not None:
            self.gates = circuit.gates
            self.input = circuit.input
            self.output = circuit.output

Solution

  • Bugs are already there - when you do self.gates = circuit.gates, circuit.gates being a list, yu point both references to the same list - and if this list is updated on the original circuit, this update will be reflected in your subcircuit instance.

    I think the most sane pattern tehre is to have an alternate constructor for the class if you have a circuit instance from which to update your own:

    from copy import copy
    class SubCircuit(Gate, Circuit):
        def __init__(self, size):
            Gate.__init__(self, size)
            Circuit.__init__(self, size)
    
        @classmethod
        def from_circuit(cls , circuit, size):
            self = SubCircuit(size)
            for key, value in circuit.__dict__.items():
                 setattr(self, key, copy(value))
            return self
    

    One "right" thing to do is to make the classes __init__ and other methods calling each other through the collaborative use of super() instead of calling then explicitly by name - however, if your classes and subclasses are fixed to these 3, that may be a bit overkill, because Python's objects not handling extra parameters passed to its own __init__ method. (So, you'd have to verify in your base classes __init__ if they are the last ones prior to object on the Method Resolution Order, and swallow the remaining args)