0

I want to model a machine with a class machine. By the way the architecture of the project has been built, the module that defines each machine is its own directory. So in directory manufacturer/machine_111/ there is a file machine.py

Class Machine(ModelX):
     def __init__(self):
     ....
     def do_something(self):
     ...

Each machine can inherit common behaviour form by being of the same model. So in manufacturer/models/ there is 'modelX.py`

Class ModelX(BaseMachine):
    ....

It also inherits from the BaseMachine class in the main directory of the project.

My problem is that, for some machines, I don't have special behaviour to inherit from the ModelX class. And some other machines are fine to be modelled with the BaseMachine class. I have more than 500 machines now and there is a lot of boilerplate code copied in the machines own directory to define or make an instance of the proper class in the hierarchy.

My goal is not to have to write the machine_111 module or the modelX.py if it is not needed. The current plan is to have some kind of conifguration file (JSON, or other format), to choose the the classes to inherit from.

In summary, I want to be able to write something like this:

a_machine = BaseMachine() # if there is no `modelX.py` and `model_11.py`
a_mahcine = ModelX() # if there is not `model_11.py` but there is `modelX.py`
a_machine = Machine() # if `model_111.py` exist

but the inheritance chain should be different. If modelX.py, 'Machineinherits from it, and if not in inherits directly fromBaseMachine`.

What tools and design patterns can I use?

2
  • I'm not sure I understand the exact inheritance hierarchies you are trying to create. But wouldn't it be sufficient to just declare class ModelX(BaseMachine): pass if you have nothing to add for Model X? Probably, ModelX = BaseMachine would also work since classes are just objects in Python. But I would strongly advice against checking the existence of various files at runtime. That's a fairly fragile approach, but may be warranted if those files are plugin-like.
    – amon
    Commented Jun 27, 2021 at 9:49
  • Sorry I don't understand the way inheritance is used here, a more elaborate example may help (please edit the question and give us some context, what is a "machine", "what a bas machine", "what behaviour is inherited", and so so). Voting to close with "needs details or clarity" until then.
    – Doc Brown
    Commented Jun 27, 2021 at 10:17

1 Answer 1

1

To answer the question directly, types in Python are first-class citizens, which means you can do the following:

Machine11_base = find_base_class("machine_11")
class Machine11(Machine11_base):
    pass

However, given the huge number of classes, you you should definitely look for a more data-driven and composable approach. An in-depth answer mandates a separate question, but here's a quick example:

class ComponentAssemblyPrototype:
    def __init__(self, name, components_args):
        self.name = name
        self.components = []
        for name, args in components_args.items():
            cls = find_component_class(name)
            self.components.add((cls, args))
    def __call__(self):
        components = {ctor.__name__ : ctor(args) for ctor, args in self.components}
        components[BasicInfo] = BasicInfo(name)
        return components

def load_machine_prototype(name):
    components_args = read_data_file(name)
    return ComponentAssemblyPrototype(name, components_args)

def grind(rock, machine):
    if (grinder := machine.get(GrinderComponent)) is not None:
        grinder.grind(rock)
    else:
        raise IncompatibleMachineError()

Machine11 = load_machine_prototype("machine_11")
machine = Machine11()
grind(some_rock, machine)

Building a proper data-driven system could take some work. As an intermediate step in that direction, you could decompose the base classes via the strategy pattern, possibly freeing the inheritance chain for something else.

class GenericMachine:
    def __init__(self, engine, tool):
        self._engine = engine
        self._tool = tool
    def grind(self, rock):
        return self._tool.get_crushing_action(self._engine.power()).apply_to(rock)

class Machine11(GenericMachine):
    def __init__(self):
        engine = find_engine("machine_11")
        tool = find_tool("machine_11")
        super().__init__(engine, tool)

TL;DR: When facing problems with inheritance, try composition instead.

1
  • Truly it seems that I have start moving to composition to encapsulate responsibilities. I am giving a try to the [Dependency Injector]{python-dependency-injector.ets-labs.org/index.html} framework a try to help with the composition/strategy patterns.
    – TeXtnik
    Commented Jun 28, 2021 at 6:06

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.