3

我正在尝试用 Python 理解 MRO。虽然这里有各种各样的帖子,但我并没有特别得到我想要的东西。考虑两个类AB派生自BaseClass,每个类都有__init__不同的参数。

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):
        ...

问题是,如果我需要CA和派生第三类,如果必须的话,我B该如何初始化__init__?我可以安全地CB或派生A

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

上面的实现给了我一个错误。

4

5 回答 5

1

我相信你不能用super这个。您必须使用“旧样式”:

class C(A,B):
    def __init__(self, something, anotherthing, someOtherThing):
        A.__init__(self, something, anotherthing)
        B.__init__(self, someOtherThing)
于 2014-02-18T18:49:19.270 回答
1

正如 jonrsharpe 在他的帖子末尾提到的那样,我遇到的处理这种情况的最佳方法是**kwargs明确地接受和提取命名参数。

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')

需要提取的参数应该在named中传递,所以它们出现在kwargs中。

您甚至可以通过省略*args示例中的 as 来显式地仅接受命名参数,因此如果您忘记了,您会遇到 TypeError。

编辑:

经过一段时间的思考,我意识到我的示例对您的示例非常具体,如果您引入另一个类或更改继承,它可能会中断。为了使这一点更普遍,应该解决两件事:

BaseClass 不调用 super。

对于这个例子,这无关紧要,但如果引入了另一个类,MRO 可能会发生变化,使得在 BaseClass之后有一个类,因此它应该调用 super。这就引出了第二个问题:

object.__init__()不带参数

如果我们想让类(特别是 BaseClass)安全地放入一个通用的多重继承结构中,其中它的超级调用可能被分派到另一个类对象,我们需要在使用它们时从 kwargs 中弹出参数。

然而,这增加了另一个复杂性,因为它要求没有两个__init__函数共享相同的参数名称。我想得出的结论是,使多重继承以一般方式工作是困难的。

这是一篇关于一些细节的有趣文章(通过谷歌找到):文章

于 2014-02-18T19:19:42.530 回答
0

要理解这一点,请尝试不带任何参数:

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__()

当我们运行这个:

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

这就是这样super做的:它确保以正确的顺序调用所有内容,而不会重复。C继承自Aand B,因此它们的两个__init__方法都应该被调用,并且它们都继承自BaseClass,所以__init__也应该调用它,但只调用一次。

如果__init__被调用的方法采用不同的参数,并且无法处理额外的参数(例如*args, **kwargs),那么您将获得TypeError您所指的 s。要解决此问题,您需要确保所有方法都可以处理适当的参数。

于 2014-02-18T18:42:15.690 回答
0

虽然bj0 的答案大部分是正确的,但手动从中提取参数kwargs比必要的更复杂和尴尬。这也意味着您不会检测到何时将额外的参数传递给类构造函数之一。

最好的解决方案是接受**kwargs,但只用它来传递任何未知的参数。当这达到object(BaseClass的基数) 时,如果有不必要的参数,它将引发错误:

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)

如果您的某个类修改(或为其提供默认值)也被其基类之一使用的参数,则您可以采用特定参数并通过关键字传递它:

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

您确实需要仅使用关键字参数而不是位置参数来调用所有构造函数。例如:

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

可以为位置参数支持类似的东西,但通常这是一个坏主意,因为你不能指望参数顺序。如果您只有一个使用位置参数的类(在 中可能有未知数量的它们*args),您可能可以通过*args传入和传出每个__init__方法来完成这项工作,但是由于顺序不同,使用不同位置参数的多个类会带来麻烦当您进行多重继承时,它们可能会发生变化。

于 2014-02-18T21:25:27.057 回答
-1

感谢大家帮助我了解 MRO。下面是我的完整代码和输出。我希望这对其他人也有帮助。

类基类(对象):

    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"

类 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

类 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

类 ThirdGenDerived(FirstGenDerived,SecondGenDerived):

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

如果名称== “主要”:

    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"

输出:执行 BaseClass 我从 BaseClass Robin 调用 BaseClass 的执行实例和 SetName

我是从 BaseClass 叫我从 BaseClass 叫小约翰

使用 printName 和 printFullName 执行 FirstGenDerived

我被称为 FirstDerivedClass Robin Hood 我被称为 FirstDerivedClass,虽然我在 BaseClass 罗宾汉殿下

使用实例执行 FirstGenderived

我是从 BaseClass 调用的 我是从 FirstDerivedClass Edwards Hood 调用的

使用 printName 和 printWholeName 执行 SecondGenDerived

我被称为 SecondDerivedClass Robin Williams Hood 我被称为 SecondDerivedClass,虽然我在 BaseClass 罗宾·威廉姆斯爵士爵士

执行 ThirdGenDerived

我从 BaseClass 调用并从第三代派生类调用我从 FirstDerivedClass 调用,虽然我在 BaseClass 罗宾汉殿下

于 2014-02-18T20:33:08.930 回答