面向对象之继承
继承的介绍
继承是一种创建新类的方式,新建的类可以称为子类或派生类, 父类又可称为基类或超类, 子类会遗传父类的属性和方法
需要注意的是: 在Python中, 新建的类可以继承一个或多个父类, 在Python中, 新建的类可以继承一个或多个父类。
使用继承主要是为了解决代码冗余问题。
案例一: 类与类之间存在冗余问题
class Student(object): school = 'hnie' def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def choose_course(self): print('%s 正在选课' % self.name) class Teacher(object): school = 'hnie' def __init__(self, name, age, sex, salary, level): self.name = name self.age = age self.sex = sex self.salary = salary self.level = level def score(self): print('%s老师正在给学生打分' % self.name)
案例二: 基于继承解决类与类之间的冗余问题
class HniePeople(object): school = 'hnie' def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex class Student(HniePeople): def choose_cource(self): print('%s学生正在选课' % self.name) class Teacher(HniePeople): # 老师的空对象, 'featherwit', 18, 'female', 3000, 10 def __init__(self, name, age, sex, salary, level): # 指名道姓地跟父类HniePeople去要__init__ HniePeople.__init__(self, name, age, sex) self.salary = salary self.level = level def score(self): print('%s老师正在给学生打分' % self.name) stu1 = Student('featherwit', 18, 'female') print(stu1.__dict__) print(stu1.school) stu1.choose_cource() tea1 = Teacher('featherwit', 18, 'female', 3000, 10) print(tea1.__dict__) print(tea1.school) tea1.score()
在Python2中有经典类和新式类之分:
- 新式类: 继承了object类的子类, 以及该子类的子类和子孙类
- 经典类: 没有继承object类的子类, 以及该子类的子类和子孙类
在Python3中没有继承任何类, 那么会默认继承object类, 所以Python3中所有的类都是新式类
多继承存在的问题
大多数面向对象语言都不支持多继承, 而在Python中, 一个子类是可以同时继承多个父类的, 这固然可以带来一个子类可以对多个不同父类加以重用的好处,
但是也可能引发著名的Diamondproblem菱形问题(或称为钻石问题, 有时候也称为"死亡钻石"), 菱形其实就是对下面这种继承结构的比喻。
A类在顶部, B类和C类分别位于其下方, D类在底部将两者链接在一起形成菱形
注意: 如果两个父类都继承自object类, 那么这不算是菱形问题
class A(object): def test(self): print('from A') class B(object): def test(self): print('from B') class C(B, A): def test(self): print('from C')
这种继承结构下导致的问题称之为菱形问题: 如果A中有一个方法, B和/或C都重写了该方法, 而D没有重写它, 那么D继承的是哪个版本的方法?
class A(object): 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, C): pass obj = D() obj.test() # from B
继承原理
Python到底是如何实现继承的呢? 对于你定义的每个类, Python都会计算出一个方法解析顺序(MRO)列表, 该MRO列表就是一个简单的所有基类的线性顺序列表
python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
所以obj.test()的查找顺序是, 先从对象obj本身的属性里找方法test, 没有找到, 则参照属性查找发起者(即obj)所处类D的MRO列表来依次检索, 首先在类D中未找到, 然后再B中找到方法test
1. 由对象发起的属性查找, 会从对象自身的属性里检索, 没有则会按照对象的类.mro()规定的顺序依次找下去
2. 由类发起的属性查找, 会按照当前类的.mro()规定的顺序依次找下去
总结: 类相关的属性查找(类名.属性, 该类的对象.属性), 都是参照该类的mro
类的继承特点: 深度优先与广度优先
多继承为非菱形结构
如果多继承是非菱形继承, Python2与Python3的属性查找顺序是一样的, 都是一个分支一个分支地找下去, 然后最后找object, 这就是深度优先
class E(object): def test(self): print('from E') class F(object): def test(self): print('from F') class B(E): def test(self): print('from B') class C(F): def test(self): print('from C') class D(object): def test(self): print('from D') class A(B, C, D): pass print( A.mro()) # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class 'object'>] obj = A() print(obj.test())
多继承为菱形结构
如果继承关系为菱形结果, 那么经典类与新式类会有不同的MRO, 分别对应属性的两种查找方式: 深度优先和广度优先
当类是经典类时, 多继承情况下, 在要查找的属性不存在时, 会按照深度优先的方式查找下去
当类是新式类时, 多继承情况下, 在要查找的属性不存在时, 会按照广度优先的方式查找下去
多继承的Mixins机制
Mixins机制核心: 就是在多继承背景下, 尽可能的提升多继承的可读性。也就是说让多继承满足人的思维逻辑("is-a")的问题
Python语言可没有接口功能,但Python提供了Mixins机制,简单来说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的,并不是用来标识子类的从属"is-a"关系的,所以Mixins机制本质仍是多继承,但同样遵守”is-a”关系,
class Vehicle(object): pass class FlyableMixin(object): def fly(self): pass class CivilAircraft(FlyableMixin, Vehicle): # 民航飞机 pass class Helicopter(FlyableMixin, Vehicle): # 直升飞机 pass class Car(Vehicle): # 汽车并不能飞, 但按照上述继承关系, 汽车也能飞 pass
可以看到,上面的CivilAircraft、Helicopter类实现了多继承,不过它继承的第一个类我们起名为FlyableMixin,而不是Flyable,这个并不影响功能,但是会告诉后来读代码的人,这个类是一个Mixin类,表示混入(mix-in),这种命名方式就是用来明确地告诉别人(python语言惯用的手法),这个类是作为功能添加到子类中,而不是作为父类,它的作用同Java中的接口。所以从含义上理解,CivilAircraft、Helicopter类都只是一个Vehicle,而不是一个飞行器。
使用Mixin类实现多重继承要非常小心
1. 首先它必须表示某一种功能,而不是某个物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀
2. 其次它必须责任单一,如果有多个功能,那就写多个Mixin类,一个类可以继承多个Mixin,为了保证遵循继承的“is-a”原则,只能继承一个标识其归属含义的父类
3. 它不依赖于子类的实现
4. 子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。(比如飞机照样可以载客,就是不能飞了)
方法的重写
子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找,例如每个老师还有职称这一属性,我们就需要在Teacher类中定义该类自己的__init__覆盖父类的
class People: school='清华大学'def __init__(self,name,sex,age): self.name=name self.sex=sex self.age=age class Teacher(People): def __init__(self,name,sex,age,title): # 派生 self.name=name self.sex=sex self.age=age self.title=title def teach(self):
print('%s is teaching' %self.name)
很明显子类Teacher中__init__内的前三行又是在写重复代码,若想在子类派生出的方法内重用父类的功能,有两种实现方式
方式一: 精确的调用某一个类下的函数 -> 不依赖与继承关系
class People: def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def f1(self): print('%s say hello!' % self.name) class Teacher(People): def __init__(self, name, age, sex, level, salary): People.__init__(self, name, age, sex) self.level = level self.salary = salary tea_obj = Teacher('featherwit', 18, 'man', '1', 10000)
方式二: super()调用父类提供给自己的方法 -> 严格依赖继承关系
class People: def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def f1(self): print('%s say hello!' % self.name) class Teacher(People): def __init__(self, name, age, sex, level, salary): # super(Teacher, self).__init__(name, age, self) super().__init__(name, age, sex) # 调用的是方法, 自动传入参数 self.level = level self.salary = salary print(Teacher.mro()) tea_obj = Teacher('featherwit', 18, 'man', '1', 10000) print(tea_obj.__dict__)
调用super()会得到一个特殊的对象, 该对象会参照属性查找的发起者的mro, 去当前类的父类中找属性
super()使用的小案例
class A: def test(self): super().test() class B: def test(self): print('from B') class C(A, B): pass obj = C() obj.test() # from B print(C.mro())
obj.test()首先找到A下的test方法,执行super().test()会基于MRO列表(以C.mro()为准)当前所处的位置继续往后查找(),然后在B中找到了test方法并执行。