我试图在Python中理解MRO。虽然这里有各种各样的帖子,但我并没有特别得到我想要的东西。考虑从A
派生的两个类B
和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):
...
问题是,如果我需要从C
和A
派生第三类B
,如果必须,我该如何初始化__init__
?我可以安全地从C
或B
派生A
。
class C(A,B):
def __init__(self, something, anotherthing, someOtherThing):
super(C, self).__init__(something, anotherthing, someOtherThing)
上述实现给了我一个错误。
答案 0 :(得分: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。
在思考了一段时间后,我意识到我的示例非常特定于您的示例,如果您引入另一个类或更改继承,它可能会中断。应该解决两个问题,以使其更加通用:
对于示例,这无关紧要,但是如果引入了另一个类,则MRO可能会更改,以便在 BaseClass之后有一个类,因此它应该调用super。这导致了第二个问题:
object.__init__()
不带参数如果我们想让类(BaseClass具体)安全地放入一个通用的多继承结构,其超级调用可能被分派到另一个类或对象,我们需要弹出kwargs的参数当我们消费它们时。
这增加了另一个复杂因素,因为它要求没有两个__init__
函数共享相同的参数名称。我想最重要的是,以一般方式进行多重继承是很困难的。
以下是一篇有趣的文章(通过谷歌找到),内容涉及一些细节:article
答案 1 :(得分: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
继承自A
和B
,因此应调用他们的__init__
个方法,并且它们都从BaseClass
继承,以便__init__
也应该被调用,但只能调用一次。
如果被调用的__init__
方法使用不同的参数,并且无法处理额外的参数(例如*args, **kwargs
),则会得到您引用的TypeError
。要解决此问题,您需要确保所有方法都能处理适当的参数。
答案 2 :(得分:0)
我相信你不能使用super
。你必须使用“旧式”:
class C(A,B):
def __init__(self, something, anotherthing, someOtherThing):
A.__init__(self, something, anotherthing)
B.__init__(self, someOtherThing)
答案 3 :(得分:0)
虽然bj0's answer大部分是正确的,但是从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__
方法来实现这一点,但是多个采用不同位置参数的类会因为它们出现的顺序而出现问题,这可能会随着您进行多重继承而改变。
答案 4 :(得分:-1)
感谢大家帮助我了解MRO。下面是我的完整代码和输出。我希望这也有助于其他人。
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
类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调用的 知更鸟 使用SetName执行BaseClass的实例
我是从BaseClass调用的 我是从BaseClass调用的 小约翰
使用printName和printFullName执行FirstGenDerived
我是从FirstDerivedClass调用的 罗宾汉 我是从FirstDerivedClass调用的,虽然我在BaseClass中 Robin Hood殿下
使用实例
执行FirstGenderived我是从BaseClass调用的 我从FirstDerivedClass调用 爱德华兹胡德
使用printName和printWholeName执行SecondGenDerived
我是从SecondDerivedClass调用的 罗宾威廉姆斯胡德 我从SecondDerivedClass调用,虽然我在BaseClass中 罗宾威廉姆斯胡德爵士
执行ThirdGenDerived
我从BaseClass调用并从第三代派生类调用 我是从FirstDerivedClass调用的,虽然我在BaseClass中 Robin Hood殿下