7.6 继承的实现原理 在单继承的情况下: 类的属性查找顺序是:对象类父类...最上级父类报错 单如果是多继承的情况呢? 想了解这个,需要知道python继承的原理。 本节我们来了解下
7.6 继承的实现原理
- 在单继承的情况下:
- 类的属性查找顺序是:对象>类>父类...>最上级父类>报错
- 单如果是多继承的情况呢? 想了解这个,需要知道python继承的原理。
本节我们来了解下python继承的原理
在python中,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)的列表,
这个MRO列表就是一个简单的所有鸡肋的线性顺序列表。
可以用方法F.mro()或者F.__mro__来输出此列表(从左到右开始查找父类,知道第一个匹配这个属性的类为止)
MRO列表的构造是通过一个C3线性化算法来实现的。数学原理我们不深究,但实际上就是 合并所有父类的MRO列表并遵循如下3条准则:
- 子类会先于父类被查找
- 多个父类会根据他们在列表中的顺序被查找
- 如果对下一个类存在两个合法的选择,选择第一个父类
在JAVA和C#中子类只能继承一个父类,而python中子类可以同时继承多个父类,如果继承了多个父类,那么属性的查找方式有两种: 1.深度优先(经典类); 2.广度优先(新式类) ※其实本质就是MRO列表的排列顺序不同
- python2中的类分为两种,python3中只有新式类:
- 新式类:继承object的类,以及他的子类都称之为新式类。※新式类有F.mro()或者F.__mro__的方法来查看查找顺序
- 经典类:没有继承object的类,以及他的子类
- python3中,只有新式类:
- 新式类:即使不写明继承object类,也会默认继承object类
新式类-python2中:
class Foo(object): pass class Bar(Foo): pass
经典类python2中:
class Foo: pass class Bar(Foo): pass
新式类-python3中:
class Foo: # 默认继承object pass class Bar(Foo): pass print(Foo.__bases__) print(Bar.mro())
python3 执行结果:
(<class 'object'>,) [<class '__main__.Bar'>, <class '__main__.Foo'>, <class 'object'>]
那什么是深度优先,什么是广度优先呢?
深度优先(经典类):先一条路查到最上层父类,然后查其他的父类。
广度优先(新式类):先一条路查到最上层父类的第一层子类为止,然后查其他路径,所有子类查完,最后查最上层父类
因为是python3环境,所以我们举例说明下广度优先:
class A: def test(self): print('from A') class B(A): def test(self): print('from B') class C(A): def test(self): print('from C') class D(B): def test(self): print('from D') class E(C): def test(self): print('from E') class F(D,E): # 继承类时候写的顺序会影响查找到的顺序!!!! --> 先D的路线查找, 后E的路线查找 pass class G(E,D): # 继承类时候写的顺序会影响查找到的顺序!!!! --> 先E的路线找, 后D的路线找 pass g = G() f = F() f.test() print(F.mro()) # 新式类可以使用此方法: F.mro() or F.__mro__ g.test() print(G.mro())
执行结果,python3中是广度优先,所以最后才会查找A类:
from D [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>] from E [<class '__main__.G'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
7.7 派生的扩展:在子类中重用父类属性
子类派生的方法的时候会覆盖父类中的方法,这样我们想用父类方法的时候怎么办呢?
- 在子类派生的新方法中重用父类的方法:
- 方式1,指名道姓:在派生方法中直接调用父类的方法。这种方法不依赖于继承,也可以写不相关类的方法
方式2:super()。这种方式依赖于继承
super()在MRO列表的下一个类中查找
class Hero: def __init__(self, nickname, hp, atk): self.Nickname = nickname self.Hp = hp self.Atk = atk def attack(self, enemy): enemy.Hp -= self.Atk class Gairun(Hero): camp = 'Demacia' def __init__(self, nickname, hp, atk, weapon): # self.Nickname = nickname # self.Hp = hp # self.Atk = atk Hero.__init__(self, nickname, hp, atk) # 调用父类的方法 self.Weapon = weapon def attack(self, enemy): # 派生方法 Hero.attack(self, enemy) # 指名道姓 print('from Geren Class') class Reven(Hero): camp = 'Noxus' def __init__(self, nickname, hp, atk, weapon): super().__init__(nickname, hp, atk) # super()方式,调用父类的对象。python3 可以直接省略super()的参数 self.Weapon = weapon def attack(self, enemy): super(Reven, self).attack(enemy) # python2中,super()需要写明参数,(方法所在类名,self) print('from Reven Class') h1 = Gairun('a', 100, 10, 'sword') h2 = Reven('b', 70, 30, 'knife') print(h1.__dict__) print(h2.__dict__) h1.attack(h2) h2.attack(h1) print(h1.Hp) print(h2.Hp)
super()根据实例化对象的类的MRO列表查找
注意:super()找的并不是父类,而是MRO列表的下一个类:
class A: def f1(self): print('A') super().f1() class B: def f1(self): print('B') class C(A,B): pass print(C.mro()) # 结果:[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>] c = C() # 实例化 c.f1() # 由于继承,class C中没有f1(),进而到class A中找, # class A的f1()中调用了super().f1(),虽然B不是A的父类, # 但super是根据MRO列表查找的,所以super会寻找MRO列表的下一个类(class B)并执行
执行结果:
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>] A B