Python多重继承

关于多重继承


我比较赞成继承的本质是逐步细化,即逐步的补充原来抽象层次所欠缺的,不断补充具体的应用场景,最终达到应用层次。对于继承的原因,一般分为两种:第一种是对接口(界面约定)的实现,另一种是重载(override)。就像java语言的设计,对于接口(interface)的继承叫做实现(implements),对于普通类的继承叫做扩展(extends)。并且规定接口不能实例化,而多重继承必须是一个抽象类和多个接口类。这也是使用多重继承的一般性原则。

python中的新类和旧类


python中的继承比较麻烦。由于一开始的设计问题,python中分为新类和旧类。新类指所有继承了object对象的类,而旧类指所有没有继承object的类。

1
2
3
4
5
def NewClass(object):
pass
def OldClass:
pass

python旧类的方法继承顺序


对于旧类,多重继承中方法采用深度优先算法。按顺序从一个父类一直深度搜索,然后再搜索第二个父类,一直到找到方法为止。

  1. 当只有一层继承时:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class A:
def __init__(self):
pass
def method(self):
print 'A'
class B:
def __init__(self):
pass
def method(self):
print 'B'
class Child(A, B):
def __init__(self):
A.__init__(self)
B.__init__(self)
c = Child()
c.method()
>>>A

可以看到c.method方法实际为 A.method方法。
Child(A, B) 改变继承顺序为 Child(B, A)再观察。

1
2
3
4
5
6
7
8
class Child(B, A):
def __init__(self):
A.__init__(self)
B.__init__(self)
c = Child()
c.method()
>>>B

这时继承c.method方法实际为B.method方法。
按照继承参数列表中的顺序由前向后查找, C(A, B) => C -> A -> B

  1. 有多层继承时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A:
def method(self):
print 'A'
class AA(A):
pass
class B:
def method(self):
print 'B'
class BB(B):
pass
class Child(AA, BB):
pass
c = Child()
c.method()
>>>A

可以看到, c.method 方法实际为 A.method, 查找顺序为 C -> A -> AA
按照继承参数列表中的顺序由前向后,深度优先查找。

python旧类中的属性继承顺序


如果存在初始化函数,旧类只能通过显示调用父类的初始化函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A:
def __init__(self):
self.var = 'A'
class B:
def __init__(self):
self.var = 'B"
class Child(A, B):
def __init__(self):
A.__init__(self)
B.__init__(self)
c = Child()
print c.var
>>> B

类属性查找没有像类方法那样,采用深度优先顺序。我们知道,在python中属性是存放在 __dict__中,而初始化就是给这个字典设置值。所以,后初始化中设置的属性值会冲掉先初始化设置的值。
因此,子类的属性由最后一个初始化的父类决定。

1
2
3
4
5
6
7
8
class Child(A, B):
def __init__(self):
B.__init__(self) #修改初始化顺序
A.__init__(self)
c = Child()
print c.var
>>> A

可见,初始化顺序决定了属性的初值。

python旧类继承的问题


  1. 祖类的多次初始化
    python旧类有个很严重的问题, 就是共同祖类的多次初始化。如果继承的多个父类他们有共同祖类(父类们有相同的父类),会导致祖类的多次初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class A:
INITIAL = 0
def __init__(self):
self.__class__.INITIAL += 1
self.var = self.INITIAL
class AA(A):
def __init__(self):
A.__init__(self)
class BB(A):
def __init__(self):
A.__init__(self)
class Child(AA, BB):
def __init__(self):
AA.__init__(self)
BB.__init__(self)
c = Child()
print c.var
>>> 2

python新类的方法继承


为了解决祖类的多次初始化,以及属性与初始化顺序相关的问题(还有其他问题)。python设计了新类,并且用super隐式进行父类的初始化。

但是新类有一个问题就是,任何多重继承的子类都有一个共同的祖类object,都是菱形继承。
如果再按照深度优先,那么一些方法最终调用的都是object的方法,而不是其他父类中定义的方法。

所以新类的方法是按照 klass.__mro__中储存的类的顺序查找的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A(object):
def method(self):
print 'A'
class B(object):
def method(self):
print 'B'
class AA(A):
pass
def Child(AA, B):
pass
c = Child()
print Child.__mro__
>>>(<class '__main__.Child'>, <class '__main__.AA'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>)
c.method()
>> A

可见是按照Child.__mro__的顺序查找,而且object永远是最后一个父类。

python新类的属性继承


在新类中,通过调用super函数进行隐式的父类初始化,保持与方法继承同样的顺序,并且避免祖类的多次初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class A(object):
INITIAL = 0
def __init__(self):
self.__class__.INITIAL += 1
self.var = self.INITIAL
class AA(A):
def __init__(self):
super(AA, self).__init__()
class BB(A):
def __init__(self):
super(BB, self).__init__()
class Child(AA, BB):
def __init__(self):
super(Child, self).__init__()
c = Child()
print Child.__mro__
>>>(__main__.Child, __main__.AA, __main__.BB, __main__.A, object)
print c.var
>>>1

不仅避免了祖类A的多次初始化,又保持与方法的查找都是按照 Child.__mro__进行的。

新类的问题


问题一

super函数不能兼容旧类。因此当继承旧类时不能使用super初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A:
def __init__(self):
self.a = 'A'
def method(self):
print 'A'
class Child(A):
def __init__(self):
super(Child, self).__init__()
c = Child()
>>>TypeError: must be type, not classobj

有两个办法解决这个问题。
办法一: 利用多重继承object

1
2
3
4
5
6
7
class Child(A, object):
def __init__(self):
super(Child, self).__init__()
c = Child()
print c.method()
>>> A

办法二: 将is-a修改为has-a,并且利用__getattr__动态获取属性和方法。将继承修改为拥有关系。

1
2
3
4
5
6
7
8
9
class Child(object):
def __init__(self):
self._base = A()
def __getattr__(self, attr):
return getattr(self._base, attr)
c = Child()
print c.method()
>>> A

问题二

当继承多个拥有初始化函数__init__时,无法同时初始化。(强烈不建议这种情况下的多重继承)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class A(object):
def __init__(self, arg1):
self.arg1 = arg1
class B(object):
def __init__(self, arg2, arg3):
self.arg2 = arg2
self.arg3 = arg3
class Child(A, B):
def __init__(self):
super(Child, self).__init__('arg1')
c = Child()
print c.arg1
>>> arg1
print c.arg2
>>> AttributeError: 'Child' object has no attribute 'arg2'
print c.arg3
>>> AttributeError: 'Child' object has no attribute 'arg3'
print Child.__mro__
>>> (__main__.Child, __main__.A, __main__.B, object)

按照Child.__mro,直接调用 A.__init__初始化函数,而不会调用B.__init__初始化函数。

改变继承顺序Child(A, B)->Child(B, A)观察其变化。

1
2
3
4
5
6
7
8
9
10
class Child(B, A):
def __init__(self):
super(Child, self).__init__()
c = Child()
>>>TypeError: __init__() takes exactly 3
>uments (1 given)
print Child.__mro_
>>>(__main__.Child, __main__.B, __main__.A, object)

此时初始化需要3个参数,可见调用的是 B.__init__

有一个办法解决这个问题。
像旧类那样,显式的分别初始化父类

1
2
3
4
5
6
7
8
class Child(A, B):
def __init__(self):
A.__init__(self, 'arg1')
B.__init__(self, 'arg2', 'arg3')
c = Child()
print c.arg1, c.arg2, c.arg3
>>>'arg1', 'arg2', 'arg3'

python多重继承的一般性原则


1. 永远使用新类, 继承object; 不继承旧类。
2. 多重继承只在父类中只有一个有初始化函数的情况下。

简单来说,就是像java那样,只采用一个类为基类,其他为接口的情况下