0

I am stuck on an inheritance/dynamic method creating problem. Assume I have an abstract base class and its children, like in this simplified example:

from abc import ABC, abstractmethod

class MyBaseClass(ABC):
  def __init__(self, x: int):
    self.x = x
    self.y = None
  
  @abstractmethod
  def evaluate(self, val: float) -> float:
    pass
  
  def get_y(self) -> float:
    return self.y

class ChildClass1(MyBaseClass):
  def evaluate(self, val: float) -> float
    self.y = val*val
    return val * self.x

class ChildClass2(MyBaseClass):
  def evaluate(self, val: float) -> float
    self.y = val**0.5
    return val / self.x

# create some instances
c1 = ChildClass1(5)
c2 = ChildClass1(2)
c3 = ChildClass2(10)
c_list = [c1, c2, c3]  # save them as a list

Now I want to create another class that is also a true child of my base class, but it's evaluate() methods is a combination of other sub-class instances evaluate() methods. To achieve this I tried writing a function that creates a instance of an instantiable version of the base class and assigning a new method to it:

class GeneralChildClass(MyBaseClass):
  def evaluate(self, val: float) -> float:
    pass

def combine_objects(object_list: list) -> GeneralChildClass:
  new_c = GeneralChildClass(1)  # initial parameter doesnt matter in this example
  settattr(new_c, 'object_list', object_list)
  def new_evaluate(self, val: float) -> float:
    result = 0.0
    new_y = 0
    for c in self.object_list:
      result += c.evaluate(val)
      new_y += c.y
    result = results / len(self.object_list)
    self.y = new_y
    return result
  
  new_c.evaluate = new_evaluate.__get__(new_c, GeneralChildClass)
  return new_c

c4 = combine_objects(c_list)
c4.evaluate(4.5)
c4.get_y()

c5 = combine_objects([c4, c1])  # this should also be possible

By using _get__() the new 'merged' evaluation() function can be added to the new object instance. However I am not sure if this is conceptually the correct/good.

Any kind of feedback on the general structure of my solution to the presented problem is welcome!

NMme
  • 461
  • 3
  • 12
  • 1
    Methods *must belong to the class to function as methods*, that is, to do the automatic binding of the first argument to an instance when called on the instance. This happens through the descriptor protocol. If you want to add a "method" to an instance namespace, you have to handle that manually, partially applying it if you must. – juanpa.arrivillaga Sep 22 '21 at 10:50
  • 1
    Your `self` in this case is going to be `new_c`, so you already have a reference to it, and don't need it as a function parameter to `new_evaluate`. – khelwood Sep 22 '21 at 10:51
  • 1
    The inheritance isn't really relevant here. More importantly, conceptually, it doesn't make sense. You just want a seperate class which acts as a container of `MyBaseClass` objects. – juanpa.arrivillaga Sep 22 '21 at 10:53
  • Thank you for the feedback I appreciate it. @khelwood so I can just leave out the `self` as a function parameter and it will still reference the object automatically where ever `self` appears within the `new_evaluate()` function? @juanpa.arrivillaga thanks for the explanation on why this wont work as a method. yes you are right, the `self` reference problem does not necessarily relate to inheritance. But I am also looking for feedback if the overall structure where this appears in makes sense. So is this a "legit" way of creating a function and adding it to the object in this way? – NMme Sep 22 '21 at 11:02
  • 1
    @NMme "it will still reference the object automatically where ever self appears within the new_evaluate()" -- No, `new_c` references the object. If you want `self` to reference the object you could write `self = new_c`, but the variable name is arbitrary. Wherever you want to refer to self in new_evaluate, you could just use `new_c`. – khelwood Sep 22 '21 at 11:05
  • Okay, I see. Thank you very much for pointing me in the right direction! – NMme Sep 22 '21 at 11:11
  • 1
    I think that what you need is to inherit the new class from the two children as follows: ``` class CombinedChild(ChildClass1, ChildClass2): def evaluate(self, val: float) -> float: v1 = ChildClass1.evaluate(self, val) v2 = ChildClass2.evaluate(self, val) return_value = v1 + v2 return return_value cc = CombinedChild(11) cc.evaluate() ``` I don't think this question is a duplicate – Boris Gorelik Sep 22 '21 at 11:16
  • @juanpa.arrivillaga Sorry I don't really understand why this doesn't make sense conceptually, can you please elaborate a little? It think I didn't make it clear enough that I really want the resulting class to be a 'true' child class of the `MyBaseClass` such that other methods beside the one presented in this very simplified example here are still available and it can be treated like any other instance of the class and child classes. So I dont think I am looking just for something that acts like a container. – NMme Sep 22 '21 at 11:18
  • @BorisGorelik thanks for the feedback, that's an interesting idea! How would I go with this if I wanted to 'merge' the `evaluation()` function of multiple instances and not just of one child class each? Maybe I should change the question title as the question I am asking there really is a duplicate – NMme Sep 22 '21 at 11:27
  • It sounds like you just need a regular class, but one that uses the Composite Design Pattern to operate on a collection of other instances. – quamrana Sep 22 '21 at 11:32
  • Also, can you give a simple example of how your new composite class should be a **true** subclass of the base class. Will it depend on the `self.x` that you already show? – quamrana Sep 22 '21 at 11:35
  • @quamrana thanks, I will look into Composite Design Pattern. To your question: yes, I basically want the resulting class to have some attributes and methods I have defined in the base class. I will extend the example a little to give you a better idea. – NMme Sep 22 '21 at 11:40
  • I added a simple example of having some additional attribute `y` and a simple getter-method for it. With the solution I got for my original question from the mentioned duplicate, the way I am doing it now works. I am still curious to hear your thoughts on it though – NMme Sep 22 '21 at 12:01

1 Answers1

1

There are a few things I would want to change about your design: First have a different abstract base class which encapsulates the real base parts (self.x is not one of them). Second you can have an intermediate base which sets up the self.x

Lastly a composite class.

from abc import ABC, abstractmethod

class MyBaseClass(ABC):
  def __init__(self):
    self.y = None
  
  @abstractmethod
  def evaluate(self, val: float) -> float:
    pass
  
  def get_y(self) -> float:
    return self.y

class ChildBase(MyBaseClass):
  def __init__(self, x: int):
    super().__init__()
    self.x = x

class ChildClass1(ChildBase):
  def evaluate(self, val: float) -> float
    self.y = val*val
    return val * self.x

class ChildClass2(ChildBase):
  def evaluate(self, val: float) -> float
    self.y = val**0.5
    return val / self.x

class Composite(MyBaseClass):
  def __init__(self, object_list):
    super().__init__()
    self.object_list = object_list

  def evaluate(self, val: float) -> float:
    result = 0.0
    new_y = 0
    for c in self.object_list:
      result += c.evaluate(val)
      new_y += c.y
    result = result / len(self.object_list)
    self.y = new_y
    return result

# create some instances
c1 = ChildClass1(5)
c2 = ChildClass1(2)
c3 = ChildClass2(10)
c_list = [c1, c2, c3]  # save them as a list
c5 = Composite(c_list)
print(c5.evaluate(4))

Also note that your self.y needs some attention.

quamrana
  • 37,849
  • 12
  • 53
  • 71
  • Thank you! This looks much simpler and nicer! Although I think you meant to name the method in the `Composite` class `evaluate()` instead of `new_evaluate()` – NMme Sep 22 '21 at 12:59
  • 1
    Sorry, yes. I'll amend that. Was in a rush . . . – quamrana Sep 22 '21 at 13:17