Method Resolution Order in case of Base Classes Having different init params

前端 未结 5 1174
刺人心
刺人心 2021-01-20 05:52

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

相关标签:
5条回答
  • 2021-01-20 06:05

    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

    0 讨论(0)
  • 2021-01-20 06:08

    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

    0 讨论(0)
  • 2021-01-20 06:11

    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.

    0 讨论(0)
  • 2021-01-20 06:11

    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)
    
    0 讨论(0)
  • 2021-01-20 06:13

    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.

    0 讨论(0)
提交回复
热议问题