python支持多继承,python的多继承背后依靠的是“继承链”(非法官方说法),查看一个类的继承链可以通过__mro__内置方法查看

class Base:
    def __init__(self):
        print('Base.__init__')


class A(Base):
    def __init__(self):
        self.a='a实例属性'
        super().__init__() # 1
        print('A.__init__')


class B(Base):
    def __init__(self):
        super().__init__() # 2
        print('B.__init__')


class C(A,B):
    def __init__(self):
        super(C,self).__init__()
        # B.__init__(self)
        print('C.__init__')


if __name__ == '__main__':
    c = C()

你会觉得上面例子输出什么?

如果你觉得会输出:Base.__init__、A.__init__、C.__init__ ;哈哈那就和我一开始一样想的比较天真。

其实答案是:Base.__init__、B.__init__、A.__init__、C.__init__

如果你初学多继承会感觉到很诧异,为什么B也会被输出,其实这就是上面提到的继承链,我来打印下C函数的继承链,得到如下结果:

print(C.__mro__)

'''
(<class '__main__.C'>, <class '__main__.A'>, 
<class '__main__.B'>, <class '__main__.Base'>, 
<class 'object'>)
'''

按照我一开始的想法,C调用super()只会调用继承元组里面的第一个继承类,如果你不以其他类名.方法(self)的形式调用是不会执行的,但是仔细想:根据继承链的元组表述,C调用super后确实是进入了A类的构造方法,此时A的__init__()方法同样调取了super();此时我们在A类中来打印下这个self的引用:

print(type(self))
print(self)

'''
<class '__main__.C'>
<__main__.C object at 0x0000013D3CF0AE50>
'''

你会发现这个self指向C类的引用,至此你是不是开始懂了,所以根据继承链:进入A类的self仍然指向C所以此时调用super进入B类,而B类同样执行super,不要慌,这个self不还是C类的引用吗?所以B类的super调用后进入Base类输出”Base.__init__“,然后输出B.__init__继续A.__init__最后C.__init__

好像搞懂了,也没什么难的

再来看一个例子:

class SetOnceMappingMixin:

    __slots__ = () #将实例属性放入元组,节省内存

    def __setitem__(self, key, value):
        if key in self:
            raise KeyError(str(key) + ' already set')
        return super().__setitem__(key, value)

class SetOnceDefaultDict(SetOnceMappingMixin, collections.defaultdict):
    pass


s=SetOnceDefaultDict(list)

在这里,SetOnceMappingMixin是一个混用类,SetOnceDefaultDict继承该类,实例化SetOnceDefaultDict的对象s,输出s是一个SetOnceDefaultDict(<class 'list'>, {}),因为继承了collections.defaultdict类,而我们自己的类和SetOnceMappingMixin类没有重写__repr()__方法,所以输出collections.defaultdict的__repr__

接下来:

d['x']=1
d['x']=2

根据继承链:SetOnceDefaultDict->SetOnceMappingMixin-> collections.defaultdict->dict->object

找到第一个身上有__setitem__方法的类就停止寻找,在SetOnceMappingMixin身上发现了,但是调用了super,也就是collections.defaultdict和dict,而字典类型是肯定有__setitem__方法的所以很自然的就可以插入键值对{ 'x' : 1},但是第二次就会报错:KeyError: 'a already set'。

假设我们在修改一下代码:

class SetOnceMappingMixin:
    '''
    Only allow a key to be set once.
    '''
    __slots__ = ()

    def __setitem__(self, key, value):
        print(1)
        if key in self:
            raise KeyError(str(key) + ' already set')
        super().__setitem__(key, value)

class SetOnceDefaultDict(SetOnceMappingMixin,collections.defaultdict):
    def __setitem__(self, key, value):
        print(2)
        super(SetOnceDefaultDict, self).__setitem__(key,value)

注意第一次的继承顺序:SetOnceMappingMixin -> collections.defaultdict

添加元素后控制台输出:2、1

第二次将继承顺序调换:collections.defaultdict-> SetOnceMappingMixin

添加元素后控制台输出:2

这个微妙的变化就是因为继承链发生了变化:第一次顺序:发现自己有__setitem__就调用自己的输出2,后需要调用super(),根据继承链进入 SetOnceMappingMixin t输出1,又调用super调用dict的__setitem__将数据存储;而第二次因为调换了顺序在输出2后直接super调collections.defaultdict就可以存储数据,所以没有再打印1

由上述的两个小案例就看出,混用类最好放在第一位,避免被其他抽象基类或超类的同名方法覆盖

最后,你应该知道下面为什么会这样了:

class Test(collections.defaultdict):
    def __getitem__(self, item):
        return 12

t=Test(list)
t['name']='Hello'
print(t['name']) #12
obj={}
obj.update(t)
print(obj['name']) #Hello