【目录】
一、继承介绍
二、继承与抽象
三、属性查找
四、继承的实现原理
1、菱形问题
2、继承原理
3、深度优先和广度优先
4、python Mixins机制
五、派生与方法重用
六、组合
一、继承介绍
1、什么是继承
(1)继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,
新建的类可称为子类或派生类,父类又可称为基类或超类
class ParentClass1: #定义父类
pass
class ParentClass2: #定义父类
pass
class SubClass1(ParentClass1): #单继承
pass
class SubClass2(ParentClass1,ParentClass2): #多继承
pass
(2)查看类继承的所有父类:通过类的内置属性__bases__可以查看类继承的所有父类
>>> SubClass2.__bases__(, )
(3)经典类与新式类
在Python2中有经典类与新式类之分——
没有显式地继承object类的类,以及该类的子类,都是经典类;
显式地继承object的类,以及该类的子类,都是新式类。
>>> ParentClass1.__bases__(,)
>>> ParentClass2.__bases__(,)
而在Python3中,即使没有显式地继承object类,也会默认继承该类。
因而在python3中统一都是新式类,关于经典类与新式类的区别,未完待续。
提示:object类提供了一些常用内置方法的实现,如用来在打印对象时返回字符串的内置方法__str__
2、为什么要继承
为了解决类与类之间,代码冗余的问题
3、python中多继承的优缺点
# 优点:
子类可以同时遗传多个父类的属性,最大限度地重用代码
# 缺点:
# 1、违背人的思维习惯:继承表达的是一种什么"是"什么的关系
# 2、代码可读性会变差
# 3、不建议使用多继承,有可能会引发可恶的菱形问题,扩展性变差,
# 如果真的涉及到一个子类不可避免地要重用多个父类的属性,应该使用Mixins机制
二、继承与抽象
1、继承和抽象的理论方法
要找出类与类之间的继承关系,需要先抽象,再继承。
抽象即总结相似之处,总结对象之间的相似之处得到类(子类),
总结类与类之间的相似之处就可以得到父类。
2、如何实现继承
【栗子】
以学生和老师的信息管理为例,学生类与老师类之间存在信息参数冗余问题:
#示范1:类与类之间存在冗余问题
classStudent:
school='OLDBOY'
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sexdefchoose_course(self):print('学生%s 正在选课' %self.name)classTeacher:
school='OLDBOY'
def __init__(self,name,age,sex,salary,level):
self.name=name
self.age=age
self.sex=sex
self.salary=salary
self.level=leveldefscore(self):print('老师 %s 正在给学生打分' %self.name)
基于继承解决类与类之间的冗余问题:
#示范2:基于继承解决类与类之间的冗余问题
classOldboyPeople:
school= 'OLDBOY'
def __init__(self, name, age, sex):
self.name=name
self.age=age
self.sex=sexclassStudent(OldboyPeople):defchoose_course(self):print('学生%s 正在选课' %self.name)
stu_obj= Student('lili', 18, 'female')print(stu_obj.__dict__)print(stu_obj.school)
stu_obj.choose_course()classTeacher(OldboyPeople):#老师的空对象,'egon',18,'male',3000,10
def __init__(self, name, age, sex, salary, level):#指名道姓地跟父类OldboyPeople去要__init__
OldboyPeople.__init__(self,name,age, sex)
self.salary=salary
self.level=leveldefscore(self):print('老师 %s 正在给学生打分' %self.name)
tea_obj=Teacher('egon',18,'male',3000,10)print(tea_obj.__dict__)print(tea_obj.school)
tea_obj.score()#输出结果:
{'name': 'lili', 'age': 18, 'sex': 'female'}
OLDBOY
学生lili 正在选课
{'name': 'egon', 'age': 18, 'sex': 'male', 'salary': 3000, 'level': 10}
OLDBOY
老师 egon 正在给学生打分
三、属性查找
有了继承关系,对象在查找属性时,先从对象自己的__dict__中找,如果没有则去子类中找,然后再去父类中找……
1、单继承背景下的属性查找
【栗子1】
obj.f2()会在父类foo中找到f2,先打印 Foo.f2,
然后执行到self.f1(),即 obj.f1(),仍会按照:对象本身->类bar->父类foo的顺序依次找下去,
在类bar中找到f1,因而打印结果为 Bar.f1
#示范一:
classFoo:deff1(self):print('Foo.f1')deff2(self):print('Foo.f2')
self.f1()#obj.f1()# 调用类Bar中的f1—— 先从自身对象找,没有再去自己的类Bar中有没有f1,没有再去继承的类Foo中找
classBar(Foo):deff1(self):print('Bar.f1')
obj=Bar()
obj.f2()#输出结果:#Foo.f2#Bar.f1
【栗子2】如何实现子类和父类中,调用的 def f1(self) 不一样,即在自己类中,就只调用自己类中的 def f1(self)
方法一:Foo.f1(self)# 调用当前类Foo中的f1
## 示范二:
classFoo:deff1(self):print('Foo.f1')deff2(self):print('Foo.f2')
Foo.f1(self)#调用当前类Foo中的f1
classBar(Foo):deff1(self):print('Bar.f1')
obj=Bar()
obj.f2()#输出结果:#Foo.f2#Foo.f1
方法二:父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的方式将方法设置为私有的
即 将父类中的 f1 隐藏起来,变为父类私有的
#示范三:
classFoo:def __f1(self): #_Foo__f1
print('Foo.f1')deff2(self):print('Foo.f2')
self.__f1() #self._Foo__f1,# 调用当前类中的f1
classBar(Foo):def __f1(self): #_Bar__f1
print('Bar.f1')
obj=Bar()
obj.f2()#输出结果:#Foo.f2#Foo.f1
2、多继承的属性查找
基本原则不变,但会涉及到菱形问题。详见后文分析。
四、继承的实现原理
1、菱形问题
大多数面向对象语言都不支持多继承,而在python中,一个子类是可以同时继承多个父类的,
这固然可以带来一个子类可以对多个不同父类加以重用的好处,
但也有可能引发著名的 diamond problem菱形问题(或称钻石问题,有时候也被称为“死亡钻石”),
菱形,其实就是对下面这种继承结构的形象比喻:
a类在顶部,b类和c类分别位于其下方,d类在底部将两者连接在一起形成菱形
这种继承结构下导致的问题称之为菱形问题:
如果a中有一个方法,b和/或c都重写了该方法,而d没有重写它,那么d继承的是哪个版本的方法:b的还是c的?如下所示:
classA(object): # 新式类deftest(self):print('from A')classB(A):deftest(self):print('from B')classC(A):deftest(self):print('from C')classD(B,C):passobj=D()
obj.test()#结果为:from B
要想搞明白obj.test()是如何找到方法test的,需要了解python的继承实现原理——
(提前透露查找顺序——obj.test()的查找顺序是,先从对象obj本身的属性里找方法test,没有找到,则参照属性查找的发起者(即obj)所处类d的mro列表来依次检索,首先在类d中未找到,然后再b中找到方法test)
2、继承原理
【MRO】
python到底是如何实现继承的呢?
对于你定义的每一个类,python都会计算出一个方法解析顺序(mro)列表,该mro列表就是一个简单的所有基类的线性顺序列表,如下
>>> D.mro() #新式类内置了mro方法可以查看线性列表的内容,经典类没有该内置该方法
[, , , , ]
python会在mro列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个mro列表的构造是通过一个c3线性化算法来实现的。
这个算法实际上就是合并所有父类的mro列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
【PS】
1.由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的顺序依次找下去
2.由类发起的属性查找,会按照当前类.mro()规定的顺序依次找下去
【举个栗子】以上个“菱形问题”的栗子为基础——
#【test1】
classA(object):deftest(self):print('from A')classB(A):deftest(self):print('from B')classC(A):deftest(self):print('from C')classD(C,B):deftest(self):print('from D')print(D.mro()) #类D以及类D的对象访问属性都是参照该类的mro列表#输出结果:#[, , , , ]
obj1 =D()
obj1.test()#输出结果:from D
print(D.test) #输出结果:
print(C.mro()) #类C以及类C的对象访问属性都是参照该类的mro列表#输出结果:[, , ]
obj2 =C()
obj2.test()#输出结果:from C
print(C.test) #输出结果:
#【test2】
classA(object):#def test(self):
#print('from A')
pass
classB(A):deftest(self):print('from B')classC(A):#def test(self):
#print('from C')
pass
classD(C,B):#def test(self):
#print('from D')
pass
print(D.mro()) #类D以及类D的对象访问属性都是参照该类的mro列表#输出结果:#[, , , , ]
obj1 =D()
obj1.test()#输出结果:from B
print(D.test) #输出结果:
#总结:类相关的属性查找(类名.属性,该类的对象.属性),都是参照该类的mro
【菱形问题+MRO】View Code
3、深度优先和广度优先
(1)非菱形继承
如果多继承是非菱形继承,经典类与新式类的属性查找顺序一样:都是一个分支一个分支地找下去,然后最后找object类
==栗子如下:
classE:#def test(self):
#print('from E')
pass
classF:deftest(self):print('from F')classB(E):#def test(self):
#print('from B')
pass
classC(F):#def test(self):
#print('from C')
pass
classD:deftest(self):print('from D')classA(B, C, D):#def test(self):
#print('from A')
pass
#新式类
print(A.mro())#A->B->E->C->F->D->object#[, , ,#, , , ]
obj=A()
obj.test()#结果为:from F
【非菱形的多继承】View Code
(2)菱形继承
如果多继承是菱形继承,经典类与新式类的属性查找顺序不一样:
# 经典类:深度优先,会在检索第一条分支的时候就直接一条道走到黑,即会检索大脑袋(共同的父类)
# 新式类:广度优先,会在检索最后一条分支的时候检索大脑袋
==栗子如下:
经典类:深度优先——
新式类:广度优先——
class G: #在python2中,未继承object的类及其子类,都是经典类
#def test(self):
#print('from G')
pass
classE(G):#def test(self):
#print('from E')
pass
classF(G):deftest(self):print('from F')classB(E):#def test(self):
#print('from B')
pass
classC(F):deftest(self):print('from C')classD(G):deftest(self):print('from D')classA(B,C,D):#def test(self):
#print('from A')
pass
## 新式类
print(A.mro())## A->B->E->C->F->D->G->object## [, , , , ,## , , ]
obj =A()
obj.test()#输出结果:from C
#经典类:A->B->E->G->C->F->D
print(A.mro())
obj=A()
obj.test()#输出结果:from C
【菱形的多继承】View Code
(3)总结
# 多继承到底要不用?
要用,但是规避几点问题——
# 1、继承结构尽量不要过于复杂
# 2、推荐使用mixins机制:在多继承的背景下满足继承的什么"是"什么的关系
4、python Mixins机制
五、派生与方法重用
六、组合