Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple inheritance and using a method of one of the base classes

Tags:

python

I have the following code:

class A(object):
    def __init__(self):
        self.name = "A"
        super(A, self).__init__()

    def Update(self):
        print "Update A"
        self.PickTarget()

    def PickTarget(self):
        print "PickTarget A"

class B(object):
    def __init__(self):
        self.name = "B"
        super(B, self).__init__()

    def Update(self):
        print "Update B"
        self.PickTarget()

    def PickTarget(self):
        print "PickTarget B"

class C(A, B):
    def __init__(self):
        super(C, self).__init__()

    def Update(self, useA):
        if useA:
            A.Update(self)
        else:
            B.Update(self)

c = C()

c.Update(useA = True)
# prints: 
# Update A
# PickTarget A

c.Update(useA = False)
# prints:
# Update B
# PickTarget A

Why does calling C.Update with useA=False still call into A.PickTarget? How can I make this work how I want it to work (ie B.Update always calls into B.PickTarget)? I'm sure this has been asked before but my searching turned up nothing - probably because I don't know what to search for.

like image 325
combatdave Avatar asked Jul 27 '11 18:07

combatdave


2 Answers

It's because A is before B in C's base classes.

You need to use B.PickTarget(self) rather than self.PickTarget() in B.Update(self) to get this behavior. Otherwise, switch A and B in C's definition.

Edit:

If the intended behavior is for B to always call methods in B and for A to always call methods in A, it's correct to use A.method(self) instead of self.method(), as the second form doesn't imply that method is in A.

You should redesign your classes. A should have a move method that moves the robot randomly and define it's other basic behavior. B should be a subclass of A and should have a move method that calls super(B, self).move() if it doesn't have a path, and otherwise moves on the path. This is the proper way to override a method on a condition.

like image 138
agf Avatar answered Oct 03 '22 14:10

agf


Whenever a method of an object is called, python uses the "Method Resolution Order" (MRO) to determine which version of the method to call. In this case, although you have explicitly called A.Update(), A.Update() doesn't explicitly call A.PickTarget. It just calls self.PickTarget(). Since this is a C object, that's equivalent to C.PickTarget(self). C.PickTarget() is inherited, and the C MRO dictates in this case that A.PickTarget is the version of PickTarget to use.

You can look at the MRO of C like this:

>>> C.__mro__
(<class '__main__.C'>, <class 'foo.A'>, <class 'foo.B'>, <type 'object'>)

There's a super-informative article on the MRO here.


Regarding how to get the behavior you want -- well, there are lots of pretty obvious ways, and at the same time, no good ones (that I can think of). I don't think this is really a good design. The main point of multiple inheritance is that you can mix and match orthogonal methods in C, but you're trying to cram multiple versions of roughly similar methods, two from A and two from B, into one class. If you tell us more about what you're doing, maybe we can suggest a better solution. (You're also changing the signature of your method while keeping the same name. That seems dodgy to me as well.)

If you're sure that you want to do this, though, another approach you might consider is name mangling, which is designed exactly for cases like this. This does exactly what you want:

class A(object):
    def __init__(self):
        self.name = "A"
        super(A, self).__init__()

    def Update(self):
        print "Update A"
        self.__PickTarget()

    def PickTarget(self):
        print "PickTarget A"

    __PickTarget = PickTarget

class B(object):
    def __init__(self):
        self.name = "B"
        super(B, self).__init__()

    def Update(self):
        print "Update B"
        self.__PickTarget()

    def PickTarget(self):
        print "PickTarget B"

    __PickTarget = PickTarget

class C(A, B):
    def __init__(self):
        super(C, self).__init__()

    def Update(self, useA):
        if useA:
            A.Update(self)
        else:
            B.Update(self)

Output:

>>> from mangling import A, B, C
>>> c = C()
>>> c.Update(useA = True)
Update A
PickTarget A
>>> c.Update(useA = False)
Update B
PickTarget B
like image 29
senderle Avatar answered Oct 03 '22 14:10

senderle