Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mixin to override inherited method

I have a collection of classes, A1, A2, A3 etc, which all have method m(). I also have class B with method m(). I want to easily be able to create classes C1, C2, C3 etc. which call m() from class B, while also having all other properties of A1, A2, A3 etc...

The issue I'm having, however, is that in class C1 the method m() from class B should call m() from the class A1.

I'm having a hard time putting into words what I want, but the the way I'm currently thinking about doing this, is using mixins. C1 would inherit from A1, with a mix-in of B. However, I don't know how to make m() in B call the correct m() from one of the A classes.

So, my two questions:

  • Is there a name for what I'm trying to do?
  • What would be the correct way to do this?

Edit: As requested, a concrete example: The method m(p) in A1, A2, A3 etc all calculates a matrix M, for some parameters p. I want to create classes C1, C2, C3 etc, which behave in the same way as A1, A2, A3, except for method m(). The new method m() takes a longer list of parameters p, of size N, and we calculate A*.m() N times and then return the sum.

The code to calculate the sum of m() is the same for all of the A* classes. In the proposed mix-in solution above, the summing code would be in B. Both B and A1 would be inherited to form C1. However, the method m() in C1 from B would have to call A1.m().

like image 347
Mark van der Wilk Avatar asked Mar 14 '23 05:03

Mark van der Wilk


1 Answers

I think you just need super which redirects the call to a parent or sibling class (depending on the MRO).

For example:

class A1(object):
    def m(self):
        print('Calling method m of class A1')
        self.data *= 2

class A2(object):
    def m(self):
        print('Calling method m of class A2')
        self.data *= 3

class A3(object):
    def m(self):
        print('Calling method m of class A3')
        self.data *= 4

class B(object):
    def m(self, p):
        print('Calling method m of class B')
        for i in range(p):
            # You haven't specified which python you are using so I assume
            # you might need to most explicit variant of super().
            # Python3 also allows just using super().m()
            super(B, self).m()

class C1(B, A1):
    def __init__(self, value):
        self.data = value

Just testing it:

a = C1(10)
a.m(10)

prints:

Calling method m of class B
Calling method m of class A1
Calling method m of class A1
Calling method m of class A1
Calling method m of class A1
Calling method m of class A1
Calling method m of class A1
Calling method m of class A1
Calling method m of class A1
Calling method m of class A1
Calling method m of class A1

and the saved value:

a.data
# returns 10485760

defining other C works too:

class C2(B, A2):
    def __init__(self, value):
        self.data = value

a = C2(10).m(2)
#Calling method m of class B
#Calling method m of class A2
#Calling method m of class A2


class C3(B, A3):
    def __init__(self, value):
        self.data = value

a = C3(10).m(1)
#Calling method m of class B
#Calling method m of class A3

Of course you want another logic and probably need to return values from .m() instead of modifying in-place but I think you can work them out for yourself.

The word you were looking for is probably MRO (method resolution order). Hope this helps you.

Also of interest could be the documentation of super (Python2), super (Python3).

And you can always check the MRO of a class by calling the .mro() method:

print(C1.mro())
[<class '__main__.C1'>, <class '__main__.B'>, <class '__main__.A1'>, <class 'object'>]

So python starts by checking if C1 has a method m and if not checks B. B has one so it is executed. The super call then just goes again into the MRO and checks if the next class (A1) has a method m, which is then executed.

like image 65
MSeifert Avatar answered Apr 21 '23 01:04

MSeifert