1

I thought that I wanted to change the inheritence of a class but then I read this How to dynamically change base class of instances at runtime? which says that this is a bad idea. I have learnt the hard way that when lots of people on SO say don't do this but you do it anyway then you eventually regret it. However, I'm struggling to see how I can avoid it. So I would like to present my problem and see if anyone has any opinions on how one might overcome this problem.

I have two libraries Frame and Imp.

Frame:

Frame is a collection of classes that act as a kind of framework for developing tools.

There are five classes in this library.

Frame.A(metaclass=ABCMeta):

Is an abstract class that everything will inherit from.

The next level forks into two types of objects: Frame.B(A) and Frame.C(A).

Frame.C does not have any more children but Frame.B does and forks again: Frame.D(B) and Frame.E(B).

IMP: We decide that we want to use the Frame framework to create a specific tool and create two classes Imp.A(Frame.D) and Imp.B(Frame.E) objects. However it turns out that we create lots of methods that are the same in both Imp.A and Imp.B.

Since we don't want lots of repeated code we look at adding a class that has the communal methods and so Imp.A and Imp.B can both inherit from. The problem is that these functions are much too specifc to go in the Frame library and it doesn't make sense to not have Frame.D and Frame.E in the Imp library.

One could create an exact replica of Frame.D (Imp.FD) and Frame.E (Imp.FE) in Imp plus the new base class (Imp.New) so we have the following structure:

Imp.New(Frame.B)

Imp.FD(Imp.New)

Imp.FE(Imp.New)

Imp.A(Imp.FD)

Imp.B(Imp.FE)

However we now have two (almost identical) copies of two classes (Imp.FD = Frame.D and Imp.FE = Frame.E) which makes development etc a bit of a pain.

If I could just create Imp.New(Frame.B) and use Frame.D and Frame.E but simply modified to inherit from Imp.New instead of Frame.B then that would be perfect. However, aparently this is bad practice: How to dynamically change base class of instances at runtime?

Can anyone suggest a solution to my problem?

Addition: The accepted answer at Pass a parent class as an argument? shows a way of defining a class inside a function so that one can choose the parent class. I could use this technique so that Frame.D and Frame.E both have variable parent classes but it feels a little hacky and won't it muck up the help function/docstrings when people use it?

This doesn't seem like a particularly unusual situation, surely there must be some protocal for this kind of thing?

jpp
  • 159,742
  • 34
  • 281
  • 339
ojunk
  • 879
  • 8
  • 21

1 Answers1

1

You might try composition instead of inheritance or using mixins

This is probably a case for composition, but I included mixins just in case.

Here is an example using imagined Frame and Imp classes.

class Frame(object):
    def do_thing(self, stuff):
        raise NotImplementedError


class A(Frame):
    def __init__(self, thingy):
        self.thingy = thingy

    def do_thing(self, stuff):
        return stuff + self.thingy


class B(Frame):
    def __init__(self, thingy):
        self.thingy = thingy

    def do_thing(self, stuff):
        return stuff - self.thingy


class CrazyB(B):
    def __init__(self, thingy):
        self.dingus = 5
        super(CrazyB, self).__init__(thingy)

    def go_crazy(self):
        return self.dingus + self.thingy - 10**10

COMPOSITION

class Imp(object):
    def __init__(self, frame, stuff):
        self.frame = frame
        self.stuff = stuff

    def be_impish(self):
        raise NotImplementedError

    def do_frame_stuff(self):
        return self.frame.do_thing(self.stuff)

# OR ALSO COMPOSITION

class ImpAlternate(object):
    frame_class = None

    def __init__(self, frame_arg, stuff):
        self.frame = self.frame_class(frame_arg)
        self.stuff = stuff

    def be_impish(self):
        raise NotImplementedError

    def do_frame_stuff(self):
        return self.frame.do_thing(self.stuff)


class ImpA(ImpAlternate):
    frame_class = CrazyB

    def be_impish(self):
        return self.frame.go_crazy()

MIXINS

class ThingyShower(object):
    def show_thingy(self):
        try:
            thingy =  str(getattr(self, 'thingy'))
        except AttributeError:
            thingy =  'no thingy'
        return 'thingy: {}'.format(thingy)


class ImpB(B, ThingyShower):
    def __init__(self, stuff):
        super(ImpB, self).__init__(stuff)

in action:

>>> x = ImpA(11, 22)
>>> print(x.be_impish())
-9999999984
>>> print(x.do_frame_stuff())
11

>>> y = ImpB(25)
>>> print(y.do_thing(10))
-15
>>> print(y.show_thingy())
thingy: 25
e.s.
  • 1,351
  • 8
  • 12