【问题标题】:Method Resolution Order in case of Base Classes Having different init params基类具有不同 init 参数的情况下的方法解析顺序
【发布时间】:2014-02-18 18:27:39
【问题描述】:

我正在尝试理解 Python 中的 MRO。虽然这里有各种各样的帖子,但我并没有特别得到我想要的东西。考虑派生自 BaseClass 的两个类 AB,每个类都有一个采用不同参数的 __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):
        ...

问题是,如果我需要从AB 派生第三类C,如果必须,我该如何初始化__init__?我可以安全地从BA 导出C

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

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

【问题讨论】:

  • 当您提到“错误”时,请提供完整的错误回溯,以帮助其他人识别您的问题。
  • 当然,现在我已经在下面包含了完整的工作代码。我会记住以后发布任何错误...

标签: python class oop inheritance method-resolution-order


【解决方案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 来显式地只接受命名参数,如示例中所示,因此如果您忘记了,您会发现 TypeError。

编辑:

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

BaseClass 不调用 super。

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

object.__init__() 不带参数

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

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

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

【讨论】:

  • 感谢 bj0 的清晰解释。我还有一个问题。如何在 ThirdClass 上打印 mro? C(*kwargs).__mro__ 引发 AttributeError。有没有办法打印出方法解析顺序?
  • 尝试访问类本身而不是实例上的.__mro__ 属性:C.__mro__
  • 非常感谢...顺便说一句,我现在已经包含了完整的代码工作副本以供参考。
  • 我又发现了一篇关于super的好文章:rhettinger.wordpress.com/2011/05/26/super-considered-super
【解决方案2】:

我相信您不能为此使用super。您必须使用“旧样式”:

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

【讨论】:

  • 这行不通。当A.__init__ 调用super(self, A).__init__() 时,会得到B.__init__ 并导致参数不匹配的错误。
【解决方案3】:

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

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 继承自 AB,因此它们的 __init__ 方法都应该被调用,并且它们都继承自 BaseClass,因此 __init__ 也应该被调用,但只调用一次。

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

【讨论】:

  • 感谢 jonrsharpe,为您提供简单的 sn-p。但是如前所述,我已经看过上面的实现,但想看更通用的例子,如 bj0 所示。因此接受他的解决方案。
【解决方案4】:

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

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

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__ 方法来完成这项工作,但是多个类采用不同的位置参数正在自找麻烦,因为它们出现的顺序可能会在您进行多重继承时发生变化。

【讨论】:

    【解决方案5】:

    感谢大家帮助我了解 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)
    

    如果 name == "ma​​in":

        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 调用的 罗宾 使用 SetName 执行 BaseClass 的实例

    我是从 BaseClass 调用的 我是从 BaseClass 调用的 小约翰

    使用 printName 和 printFullName 执行 FirstGenDerived

    我是从 FirstDerivedClass 调用的 罗宾汉 我是从 FirstDerivedClass 调用的,尽管我在 BaseClass 中 罗宾汉殿下

    使用实例执行 FirstGenderived

    我是从 BaseClass 调用的 我是从 FirstDerivedClass 调用的 爱德华兹胡德

    使用 printName 和 printWholeName 执行 SecondGenDerived

    我是从 SecondDerivedClass 调用的 罗宾威廉姆斯胡德 我是从 SecondDerivedClass 调用的,尽管我在 BaseClass 中 罗宾威廉斯胡德爵士

    执行 ThirdGenDerived

    我从 BaseClass 调用并从第三代派生类调用 我是从 FirstDerivedClass 调用的,尽管我在 BaseClass 中 罗宾汉殿下

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-09
      • 2020-05-16
      • 1970-01-01
      • 2021-01-17
      • 1970-01-01
      • 2018-02-14
      • 2021-09-05
      相关资源
      最近更新 更多