在Python类中解决钻石继承问题

12

考虑以下 Python 代码:

class Parent(object):
    def __init__(self, name, serial_number):
        self.name = name
        self.serial_number = serial_number


class ChildA(Parent):
    def __init__(self, name, serial_number):
        self.name = name
        self.serial_number = serial_number
        super(ChildA, self).__init__(name = self.name, serial_number = self.serial_number)

    def speak(self):
        print("I am from Child A")


class ChildB(Parent):
    def __init__(self, name, serial_number):
        self.name = name
        self.serial_number = serial_number
        super(ChildB, self).__init__(name = self.name, serial_number = self.serial_number)

    def speak(self):
        print("I am from Child B")


class GrandChild(ChildA, ChildB):
    def __init__(self, a_name, b_name, a_serial_number, b_serial_number):
        self.a_name = a_name
        self.b_name = b_name
        self.a_serial_number = a_serial_number
        self.b_serial_number = b_serial_number
        super(GrandChild, self).__init_( something )

当在GrandChild类中运行super函数时,什么是正确的格式化__init__参数的方式,以便ChildA和ChildB都获得正确的参数?

同时,如何从GrandChild类中访问speak方法的两个不同版本(ChildA的版本和ChildB的版本)?


2
这似乎不是多重继承的有效用例。为什么不使用组合呢? - MSeifert
7
我认为“因乱伦导致的继承问题”会是更合适的标题。 - Ander
3个回答

13

因此,当您从孙子代(Grandchild)调用super时,ChildA__init__方法将被调用,因为super遵循__mro__属性(从左到右按照父母顺序,然后是祖父母顺序,再后面是曾祖父母,...)

由于ChildAinit也调用了super,那么所有的super调用都将被链接起来,调用child b的__init__并最终调用父init。

为了使其正常工作,您的接口通常需要保持一致。即位置参数需要意味着相同的事情,并且按照相同的顺序排列。

在这种情况下,关键字参数可能更好地工作。

class Parent:    
    def __init__(self, name, serial, **kwargs):
        self.name = name
        self.serial = serial

class ChildA(Parent):    
    def __init__(self, a_name, a_serial, **kwargs):
        self.a_name = a_name
        self.a_serial = a_serial
        super().__init__(**kwargs)

class ChildB(Parent):    
    def __init__(self, b_name, b_serial, **kwargs):
        self.b_name = b_name
        self.b_serial = b_serial
        super().__init__(**kwargs)


class GrandChild(ChildA, ChildB):
    def __init__(self):
        super().__init__(name = "blah", a_name = "a blah", b_name = "b blah", a_serial = 99, b_serial = 99, serial = 30)

还要注意,在您的代码中,名称和序列号在所有类之间被重用作实例属性,这可能不是您想要的。


ChildAChildB需要彼此意识到对方,以避免它们的参数冲突,这对我来说似乎是代码异味 - Tez

4
在Python中,你可以显式地调用父类的特定方法(之一):
ChildA.__init__(self, a_name, a_serial)
ChildB.__init__(self, b_name, b_serial)

请注意,在这种调用方式下需要明确地放置self
您也可以像之前一样使用super()的方式,它将调用“第一个”父级。确切的顺序是动态的,但默认情况下它将按照继承层次结构的从左到右、深度优先、先序扫描的顺序进行。因此,您的super()调用只会在ChildA上调用__init__

当我在GrandChild类中调用super时,它确实调用了ChildA和ChildB的__init__函数。我验证的方法是在两个__init__函数中都放置了打印语句,并且两者都被打印出来了。 - Zoey
我认为顺序不是从左到右深度优先,因为这会在第一个子节点和第二个子节点之间调用祖父节点,而实际情况并非如此。每个超级调用只会调用一个新方法,但当每个被调用的方法也调用超级时,它就会链接起来。 - Sam Hartman
我澄清了扫描顺序。顺序是从左到右,深度优先(它会在尝试第二个父级之前尝试祖父级),但它会在尝试祖父级之前尝试父级(先序扫描)。 - Niobos

0

Python中不存在钻石问题,因为它优先考虑首先被继承的类。

两个重要规则:

  1. 我们从左到右进行。

  2. 当所有子元素都被考虑时,我们转到父级。

    class Parent:    
        def __init__(self, name, serial):
            self.name = name
            self.serial = serial
    
        def printName(self):
            print("Inside P");
    
    class ChildA(Parent):    
        def __init__(self, a_name, a_serial):
            self.a_name = a_name
            self.a_serial = a_serial
    
        def printName(self):
            print("Inside A");
    
    class ChildB(Parent):    
        def __init__(self, b_name, b_serial):
            self.b_name = b_name
            self.b_serial = b_serial
    
        def printName(self):
            print("Inside B");
    
    
    class GrandChild(ChildA, ChildB):
        def __init__(self):
            super().__init__(name = "blah", a_name = "a blah", b_name = "b blah", a_serial = 99, b_serial = 99, serial = 30)
    
    obj = GrandChild();
    obj.printName();
    

    输出为Inside A

    在上面的例子中,GrandChild(ChildA,ChildB)表示类GrandChild首先继承了类ChildA,然后是类ChildB。

    如果您在GC类中切换继承的子项,则:

    class GrandChild(ChildB, ChildA):
        def __init__(self):
            super().__init__(name = "blah", a_name = "a blah", b_name = "b blah", a_serial = 99, b_serial = 99, serial = 30)
    
    obj = GrandChild();
    obj.printName();
    

    输出为Inside B


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接