Method Resolution Order in case of Base Classes Ha

2019-02-25 14:05发布

问题:

I am trying to understand MRO in Python. Although there are various posts here, I am not particularly getting what I want. Consider two classes A and B derived from BaseClass, each having an __init__ taking different params.

class BaseClass(object):
    def __init__(self):
        print "I am the base class"

class A(BaseClass):
    def __init__(self, something, anotherthing):
        super(A, self).__init__()
        self.something = something
        self.anotherthing = anotherthing
    def methodsA(self):
        ...

class B(BaseClass):
    def __init__(self, someOtherThing):
        super(B, self).__init__()
        self.someOtherThing = someOtherThing 
    def methodsB(self):
        ...

The question is, if I need to derive a Third Class C from both A and B, how do I initialise the __init__, if I have to? I can safely derive C from either B or A.

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

The above implementation gives me an error.

回答1:

As jonrsharpe mentioned at the end of his post, the best way I've come across for handling this type of situation is accepting **kwargs and extracting named arguments explicitly.

class BaseClass(object):
    def __init__(self, **kwargs):
        print("BaseClass.__init__({},{})".format('', kwargs))
        super(BaseClass,self).__init__(**kwargs)            

class A(BaseClass):
    def __init__(self, **kwargs):
        print("A.__init__({},{})".format('', kwargs))

        a = kwargs.pop('something')
        super(A,self).__init__(**kwargs)

class B(BaseClass):
    def __init__(self, **kwargs):
        print("B.__init__({},{})".format('', kwargs))       

        b = kwargs.pop('anotherthing')
        super(B,self).__init__(**kwargs)


class C(A, B):
    def __init__(self, **kwargs):
        print("C.__init__({},{})".format('', kwargs))

        super(C,self).__init__(**kwargs)


c = C(something=1,anotherthing='a')

Arguments that need to be extracted should be passed in named, so they appear in kwargs.

You can even explicitly accept only named arguments by ommitting the *args as in the example, so you catch yourself with a TypeError if you forget.

EDIT:

After thinking on it a while I realize that my example is very specific to your example, and if you introduce another class or change inheritance it may break. There are two things that should be addressed to make this more general:

BaseClass does not call super.

For the example this doesn't matter, but if another class is introduced the MRO might change such that there is a class after BaseClass and it should therefore call super. This leads to the second issue:

object.__init__() takes no parameters

If we want to make the classes (BaseClass specifically) safe to put into a generic multiple inheritance structure where its super call might be dispatched to another class or object, we need to pop arguments off kwargs when we consume them.

This adds another complication, though, in that it requires that no two __init__ functions share the same parameter name. I guess the takeaway is that making multiple inheritance work in a general way is difficult.

Here is an interesting article (found through google) about some of the details: article



回答2:

To understand this, try without any arguments:

class BaseClass(object):
    def __init__(self):
        print("BaseClass.__init__")

class A(BaseClass):
    def __init__(self):
        print("A.__init__")
        super(A, self).__init__()

class B(BaseClass):
    def __init__(self):
        print("B.__init__")
        super(B, self).__init__()

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

When we run this:

>>> c = C()
C.__init__
A.__init__
B.__init__
BaseClass.__init__

This is what super does: it makes sure everything gets called, in the right order, without duplication. C inherits from A and B, so both of their __init__ methods should get called, and they both inherit from BaseClass, so that __init__ should also be called, but only once.

If the __init__ methods being called take different arguments, and can't deal with extra arguments (e.g. *args, **kwargs), you get the TypeErrors you refer to. To fix this, you need to make sure that all the methods can handle the appropriate arguments.



回答3:

I believe you can't use super for this. You'll have to use the "old style":

class C(A,B):
    def __init__(self, something, anotherthing, someOtherThing):
        A.__init__(self, something, anotherthing)
        B.__init__(self, someOtherThing)


回答4:

While bj0's answer is mostly right, manually extracting the arguments from kwargs is more complicated and awkward than is necessary. It also means that you won't detect when extra arguments are passed in to one of the class constructors.

The best solution is to accept **kwargs, but only use it to pass on any unknown arguments. When this reaches object (BaseClass's base), it will raise an error if there were unnecessary arguments:

class BaseClass(object):
    def __init__(self, **kwargs):
        super(BaseClass, self).__init__(**kwargs) # always try to pass on unknown args

class A(BaseClass):
    def __init__(self, something, anotherthing, **kwargs): # take known arguments
        super(A, self).__init__(**kwargs) # pass on the arguments we don't understand
        self.something = something
        self.anotherthing = anotherthing

class B(BaseClass):
    def __init__(self, someOtherThing, **kwargs): # same here
        super(B, self).__init__(**kwargs) # and here
        self.someOtherThing = someOtherThing 

class C(A, B): # this will work, with someOtherThing passed from A.__init__ to B.__init__
    pass

class D(B, A): # this will also work, with B.__init__ passing on A.__init__'s arguments
    pass

import threading
class E(C, threading.Thread): # keyword arguments for Thread.__init__ will work!
    def run(self):
        print(self.something, self.anotherthing, self.someOtherThing)

If one of your classes modifies (or provides a default for) an argument that is also used by one of its base classes, you can both take a specific parameter and pass it on by keyword:

class F(C):
    def __init__(self, something, **kwargs):
        super(F, self).__init__(something="foo"+something, **kwargs)

You do need to be calling all your constructors with only keyword arguments, no positional ones. For instance:

f = F(something="something", anotherthing="bar", someOtherThing="baz")

It's possible to support something similar for positional arguments, but usually its a bad idea because you can't count on the argument order. If you had just one class that took positional arguments (perhaps an unknown number of them in *args), you could probably make that work by passing *args into and out of each __init__ method, but multiple classes taking different positional arguments is asking for trouble due to the order they appear in possibly changing as you do multiple inheritance.



回答5:

Thanks all for helping me understand MRO. Below is my complete Code together with output. I hope this will also help other's.

class BaseClass(object):

    def __init__(self, *args, **kwargs):
        self.name = kwargs.get('name')

    def printName(self):
        print "I am called from BaseClass"
        print self.name

    def setName(self, givenName):
        print "I am called from BaseClass"
        self.name=givenName

    def CalledFromThirdGen(self):
        print "I am called from BaseClass and invoked from Third Generation Derived            Class"

class FirstGenDerived(BaseClass):

    def __init__(self, *args, **kwargs):
        super(FirstGenDerived, self).__init__(*args, **kwargs)
        self.name = kwargs.get('name')
        self.FamilyName = kwargs.get('FamilyName')

    def printFullName(self):
        print "I am called from FirstDerivedClass"
        print self.name + ' ' + self.FamilyName

    def printName(self):
        print "I am called from FirstDerivedClass, although I was present in BaseClass"
        print "His Highness " + self.name + ' ' + self.FamilyName

class SecondGenDerived(BaseClass):

    def __init__(self, *args, **kwargs):
        super(SecondGenDerived, self).__init__(*args, **kwargs)
        self.name = kwargs.get('name')
        self.middleName = kwargs.get('middleName')
        self.FamilyName = kwargs.get('FamilyName')

    def printWholeName(self):
        print "I am called from SecondDerivedClass"
        print self.name + ' ' + self.middleName + ' ' + self.FamilyName

    def printName(self):
        print "I am called from SecondDerivedClass, although I was present in BaseClass"
        print "Sir " + self.name + ' ' + self.middleName + ' ' + self.FamilyName

class ThirdGenDerived(FirstGenDerived, SecondGenDerived):

    def __init__(self, *args, **kwargs):
        super(ThirdGenDerived, self).__init__(*args, **kwargs)

if name == "main":

    print "Executing BaseClass"
    BaseClass(name='Robin').printName()

    print "Executing Instance of BaseClass with SetName \n"
    Instance = BaseClass()
    Instance.setName("Little John")
    Instance.printName()
    print "################################################\n"


    print "Executing FirstGenDerived with printName and printFullName\n"
    FirstGenDerived(name='Robin', FamilyName='Hood').printFullName()
    FirstGenDerived(name='Robin', FamilyName='Hood').printName()
    print "################################################\n"


    print "Executing FirstGenderived with instance\n"
    Instance2 = FirstGenDerived(name=None, FamilyName="Hood")
    Instance2.setName("Edwards")
    Instance2.printFullName()
    print "################################################\n"

    print "Executing SecondGenDerived with printName and printWholeName\n"
    SecondGenDerived(name='Robin', FamilyName='Hood', middleName='Williams').printWholeName()
    SecondGenDerived(name='Robin', FamilyName='Hood', middleName='Williams').printName()
    print "################################################\n"

    print "Executing ThirdGenDerived\n"
    ThirdGenDerived(name='Robin', FamilyName='Hood', middleName='Williams').CalledFromThirdGen()
    ThirdGenDerived(name='Robin', FamilyName='Hood', middleName='Williams').printName()
    print "################################################\n"

Output: Executing BaseClass I am called from BaseClass Robin Executing Instance of BaseClass with SetName

I am called from BaseClass I am called from BaseClass Little John

Executing FirstGenDerived with printName and printFullName

I am called from FirstDerivedClass Robin Hood I am called from FirstDerivedClass, although I was present in BaseClass His Highness Robin Hood

Executing FirstGenderived with instance

I am called from BaseClass I am called from FirstDerivedClass Edwards Hood

Executing SecondGenDerived with printName and printWholeName

I am called from SecondDerivedClass Robin Williams Hood I am called from SecondDerivedClass, although I was present in BaseClass Sir Robin Williams Hood

Executing ThirdGenDerived

I am called from BaseClass and invoked from Third Generation Derived Class I am called from FirstDerivedClass, although I was present in BaseClass His Highness Robin Hood