Python从菱形继承到多继承

一、菱形继承与多继承

Python在继承方面与Java不同,Python可以进行多继承,而Java则不能多继承,替代的是使用的多接口方法。Python继承问题中,最经典的一个问题是菱形继承,然而菱形继承是多继承中的一个特殊的例子。从下图中可以看到他们之间的差异和联系。后面将使用更加普遍的多继承来说明这些继承会遇到的特殊情况。
image

二、多继承可能面临的问题

从前面的介绍可以看到Python多继承存在一些问题如下:

  • 在Class C1中调用的方法,在Class B1和Class B2有同样的实现该如何选择。
  • 多继承的继承顺序问题。
  • 继承过程中有super类名继承这两者有什么问题。
  • 继承过程中初始化的参数该如何申明
  • 继承过程中想通过初始化类进行参数传递如何实现。
  • ...
    为了针对上面出现的若干问题,将对继承进行一些实验来说明上述的问题,对于方法操作介绍的很多,这里对属性的操作进行描述。

三、多继承实现的方法

在Python中多继承可以通过两种方法实现,第一个是类名继承,第二个是super,具体如下:

3.1 类名继承

类名继承的简单使用如下,注意:类名继承的过程中会初始化两次父类,这个问题可以通过super解决。

class A1:
    def __init__(self, a1):
        self.a1 = a1
        self.a_self = 8
        print(a1)


class B1(A1):
    def __init__(self, b1, *args):
        # A1.__init__(self, *args) # super继承全参数传递
        A1.__init__(self, b1)
        self.b_self = 5
        self.a_self = 9
        print(b1)


class C1(A1):
    def __init__(self, c1, *args):
        # A1.__init__(self, *args) # super继承全参数传递
        A1.__init__(self, c1)
        self.c_self = 7
        self.a_self = 10
        print(c1)


class D1(B1, C1):
    def __init__(self, d1, d2):
        C1.__init__(self, d1)
        B1.__init__(self, d2)
        # C1.__init__(self, d1)
        # super(D1, self).__init__(d1, d2) # super继承

    def show(self):
        print(self.a_self)
        print(self.b_self)
        print(self.c_self)


d = D1("1", "2")
# 使用super继承的结果是1,2
# 使用类名继承的结果是1 1 2 2 A1被初始化了两次
# d.show()
print(D1.__mro__)

3.2 super继承

super继承的简单使用如下,值得注意的是在super下的多继承需要使用*args进行参数传递保证初始化不会出现问题,并且可以解决类名继承初始化两次的问题。

class A:
    def __init__(self, a):
        self.a = a
        self.b = 3
        print(a, "A")

    def show(self):
        print(self.a)
        print("A show()")


class B(A):
    def __init__(self, b1, b2, *args):
        print(b1, b2, "B")
        super(B, self).__init__(*args)
        self.b2 = b2
        self.b1 = b1
        self.a = 999

    def show(self):
        print(self.b2, self.b1, self.a)
        print("B show()")


class C(A):
    def __init__(self, c1, c2, *args):
        print(c1, c2, "C")
        super(C, self).__init__(*args)
        self.c2 = c2
        self.c1 = c1
        self.a = 9991

    def show(self):
        print("C show()")


class D(C, B):
    def __init__(self, d1, d2, d3, d4, d5):
        super(D, self).__init__(d1, d2, d3, d4, d5)
        print(d1)
        print(d2)
        self.d1 = d1
        self.d2 = d2
		
		
d = D(2, 3, 4, 5, 6)
print(d)
print(d.a)
d.show()
print(D.__mro__)
# 输出结果
"""
2 3 C
4 5 B
6 A
2
3
<__main__.D object at 0x0000025B243C09C8>
9991
C show()
(<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
"""

3.3 super继承和类名继承的性质

super继承的性质:

  • 按照从左到右的顺序继承,先查询自己有没有方法,没有方法按照从左到右的顺序从父类中查询,最后再去祖类查找。
  • 对于类属性而言,同样的道理,但是属性在过程中可以进行操作,例如在A的中一个属性为10,在B中和C中分别加100,200,在传给D时就变成了310。

对于第一点比较好理解,方法就是有就从自己往上搜索。在属性上有一个点,就是我在A类申明了一个变量,通过类的不断初始化,初始化(B,C)我可以将这个变量传递给D。这个时候就会出现一个情况,执行的结果是从祖类一步步递归下来。参考如下:

class A:
    def __init__(self, a1):
        self.a = a1
        self.self_a = 10
        print("A")

    def show(self):
        print(self, "A show()")


class B(A):
    def __init__(self, b1, b2, *args):
        super(B, self).__init__(*args)
        self.b1 = b1
        self.b2 = b2
        print("B", self.self_a)  # 1210
        self.self_a += 100
        print("B")

    def show(self):
        print(self, "B show()")


class C(A):
    def __init__(self, c1, c2, *args):
        super(C, self).__init__(*args)
        self.c1 = c1
        self.c2 = c2 
        print("C", self.self_a)  # 210
        self.self_a += 1000
        print("C")

    def show(self):
        print(self, "C show()")


class E(A):
    def __init__(self, *args):
        super(E, self).__init__(*args)
        self.self_a += 200
        print(self.show())  # none,因为没有初始化,不会执行到这个方法

    def show(self):
        print("E show()")


class D(B, C, E):
    def __init__(self, d1, d2, *args):
        super(D, self).__init__(*args)
        self.d1 = d1
        self.d2 = d2
        print("D")

    # def show(self):
    #     print(self)
    #     print("D show()")


d = D(1, 2, 3, 4, 5, 6, 7)
d.show()
print(d.self_a)
print(D.__mro__)
"""
A
<__main__.D object at 0x000001FEC09B0BA8> B show()
None
C 210
C
B 1210
B
D
<__main__.D object at 0x000001FEC09B0BA8> B show()
1310
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.E'>, <class '__main__.A'>, <class 'object'>)
"""

对于上述的结果,有人可能会疑惑:不是先初始化B,在初始化C,所以B的结果是10。这是由于B,C中都没有这个变量,因此它会一直按顺序找,再去祖类中找,而这种执行方式是递归调用,由于B、C、E都没有因此去了A,然后在E,C,B的递归调用,因此在B的时候结果是1210。

类名继承的性质:

  • 不用传递全部参数
  • 初始化过程由自己控制

上述有一个问题,就是类名继承是会初始化两次的,因此在继承顺序中,后初始化的结果会把先计算的结果覆盖掉,参考如下:

class A:
    def __init__(self, a1):
        self.a = a1
        self.self_a = 10
        print("A")

    def show(self):
        print(self, "A show()")


class B(A):
    def __init__(self, b1, b2, *args):
        # super(B, self).__init__(*args)
        A.__init__(self, 10)
        self.b1 = b1
        self.b2 = b2
        # print("B", self.self_a)
        print("B")
        self.self_a += 100

    def show(self):
        print(self, "B show()")


class C(A):
    def __init__(self, c1, c2, *args):
        # super(C, self).__init__(*args)
        A.__init__(self, 20)
        self.c1 = c1
        self.c2 = c2
        print("C")
        # self.self_a += 10000

    def show(self):
        print(self, "C show()")


class D(C, B):
    def __init__(self, d1, d2, *args):
        # super(D, self).__init__(*args)
        # C.__init__(self, 3, 5) # 先初始化C,不会覆盖B的累加结果
        # print("C", self.self_a) # self_a的输出结果为110
        B.__init__(self, 1, 2)
        print("B", self.self_a) 
        C.__init__(self, 3, 5)  # 由于重新初始化会覆盖B累加的结果
        print("C", self.self_a) # self_a的输出结果为10
        self.d1 = d1
        self.d2 = d2
        print("D")

    def show(self):
        print(self)
        print("D show()")


d = D(1, 2, 3, 4, 5, 6, 7)
# d.show()
print(D.__mro__)
print(d.self_a)
"""
A
B
B 110
A
C
C 10
D
(<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
10  # 
"""

四、总结

super继承和类名继承的方法,会按照从自己出发逐步往父类走,走到第一个就算结束。对于变量而言,会递归进行,其顺序和类名继承的顺序一致。而Python采取的是C3的继承算法。注:虽然多继承的问题可以通过逐步分析,以及调整代码的顺序解决问题,但是由于实际工程中其过于复杂,尽可能不使用,采取更加优化的解决方法为好,并且通过设计模式和规范进行约束。例如将属性私有化,不允许修改。

参考

博客园-Python的多继承问题-MRO和C3算法

知乎-Python多重继承问题-MRO和C3算法

CSDN-Python多继承与super使用详解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章