一、继承的介绍
1.1 什么是继承
I:继承是一种创建新类的方式,新建的类可称为子类或派生类,父类又可称为基类或超类,子类会遗传父类的属性
II:需要注意的是:python支持多继承
在Python中,新建的类可以继承一个或多个父类
class Parent1(object): x=1111 class Parent2(object): pass class Sub1(Parent1): # 单继承 pass class Sub2(Parent1,Parent2): # 多继承 pass print(Sub1.__bases__) # (<class '__main__.Parent1'>,) print(Sub2.__bases__) # (<class '__main__.Parent1'>, <class '__main__.Parent2'>) print(Sub1.x) # 111
ps1: 在python2中有经典类与新式类之分
新式类:继承了object类的子类,以及该子类的子类子子类……
经典:没有继承object类的子类,以及该子类的子类子子类……
ps2:在python3中没有继承任何类,那么会默认继承object类,所以python3中所有的类都是新式类
print(Parent1.__bases__) # (<class 'object'>,) print(Parent2.__bases__) # (<class 'object'>,)
III:python的多继承
优点:子类可以同时遗传多个父类的属性,最大限度地重用代码
缺点:
1、违背人的思维习惯:继承表达的是一种什么"是"什么的关系
2、代码可读性会变差
3、不建议使用多继承,有可能会引发可恶的菱形问题,扩展性变差,
如果真的涉及到一个子类不可避免地要重用多个父类的属性,应该使用Mixins
1.2 为何要继承
用来解决类与类之间代码冗余问题
1.3 如何实现继承
示范一:类与类之间存在冗余问题
# 示范1:类与类之间存在冗余问题 class Student: school='OLDBOY' 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: school='OLDBOY' 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 OldboyPeople: school = 'OLDBOY' def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex class Student(OldboyPeople): def choose_course(self): print('学生%s 正在选课' % self.name) stu_obj = Student('lili', 18, 'female') print(stu_obj.__dict__) print(stu_obj.school) stu_obj.choose_course() class Teacher(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 = level def score(self): print('老师 %s 正在给学生打分' % self.name) tea_obj=Teacher('egon',18,'male',3000,10) tea_obj.score()
二、继承下的属性查找
2.1 单继承下的属性查找
# 单继承背景下的属性查找 # 示范一: class Foo: def f1(self): print('Foo.f1') def f2(self): print('Foo.f2') self.f1() # obj.f1() class Bar(Foo): def f1(self): print('Bar.f1') obj=Bar() obj.f2() # Foo.f2 # Foo.f1 # 示范二: class Foo: def f1(self): print('Foo.f1') def f2(self): print('Foo.f2') Foo.f1(self) # 调用当前类中的f1 class Bar(Foo): def f1(self): print('Bar.f1') obj=Bar() obj.f2() # Foo.f2 # Foo.f1 # 示范三: class Foo: def __f1(self): # _Foo__f1 print('Foo.f1') def f2(self): print('Foo.f2') self.__f1() # self._Foo__f1,# 调用当前类中的f1 class Bar(Foo): def __f1(self): # _Bar__f1 print('Bar.f1') obj=Bar() obj.f2() # Foo.f2 # Foo.f1
2.2 多继承下的属性查找
多继承可分为菱形问题与非菱形问题
2.2.1 mro方法
在python中,新式类可以直接使用mro方法来查看mro列表,每个类均参照其mro列表来查看属性
class A(object): # def test(self): # print('from A') pass class B(A): def test(self): print('from B') pass class C(A): # def test(self): # print('from C') pass class D(C,B): # def test(self): # print('from D') pass print(D.mro()) # 类D以及类D的对象访问属性都是参照该类的mro列表 # [<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>] # obj = D() # obj.test() # print(D.test) print(C.mro()) # 类C以及类C的对象访问属性都是参照该类的mro列表 # [<class '__main__.C'>, <class '__main__.A'>, <class 'object'>] # c=C() # c.test() # 总结:类相关的属性查找(类名.属性,该类的对象.属性),都是参照该类的mro
2.2.2 非菱形多继承
如果多继承是非菱形继承,经典类与新式的属性查找顺序一样:
都是一个分支一个分支地找下去,然后最后找object
class E: # def test(self): # print('from E') pass class F: def test(self): print('from F') class B(E): # def test(self): # print('from B') pass class C(F): # def test(self): # print('from C') pass class D: def test(self): print('from D') class A(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
2.2.3 菱形问题多继承
如果多继承是菱形继承,经典类与新式类的属性查找顺序不一样:
经典类:深度优先,会在检索第一条分支的时候就直接一条道走到黑,即会检索大脑袋(共同的父类)
新式类:广度优先,会在检索最后一条分支的时候检索大脑袋
class G: # 在python2中,未继承object的类及其子类,都是经典类 # def test(self): # print('from G') pass class E(G): # def test(self): # print('from E') pass class F(G): def test(self): print('from F') class B(E): # def test(self): # print('from B') pass class C(F): def test(self): print('from C') class D(G): def test(self): print('from D') class A(B,C,D): # def test(self): # print('from A') pass # 新式类 # print(A.mro()) # A->B->E->C->F->D->G->object # 经典类:A->B->E->G->C->F->D obj = A() obj.test()
三、Python的mixins机制
3.1 mixins机制介绍
一个子类可以同时继承多个父类,这样的设计常被人诟病,一来它有可能导致可恶的菱形问题,二来在人的世界观里继承应该是个”is-a”关系。 比如轿车类之所以可以继承交通工具类,是因为基于人的世界观,我们可以说:轿车是一个(“is-a”)交通工具,而在人的世界观里,一个物品不可能是多种不同的东西,因此多重继承在人的世界观里是说不通的,它仅仅只是代码层面的逻辑。不过有没有这种情况,一个类的确是需要继承多个类呢?
mixins机制核心:就是在多继承背景下尽可能地提升多继承的可读性(让多继承满足人的思维习惯=》什么"是"什么)
3.2 mixins一般使用
民航飞机、直升飞机、轿车都是一个(is-a)交通工具,前两者都有一个功能是飞行fly,但是轿车没有,所以如下所示我们把飞行功能放到交通工具这个父类中是不合理的
class Vehicle: # 交通工具 def fly(self): ''' 飞行功能相应的代码 ''' print("I am flying") class CivilAircraft(Vehicle): # 民航飞机 pass class Helicopter(Vehicle): # 直升飞机 pass class Car(Vehicle): # 汽车并不会飞,但按照上述继承关系,汽车也能飞了 pass
Python提供了Mixins机制,简单来说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的,并不是用来标识子类的从属"is-a"关系的,所以Mixins机制本质仍是多继承,但同样遵守”is-a”关系,如下
class Vehicle: # 交通工具 pass class FlyableMixin: def fly(self): ''' 飞行功能相应的代码 ''' print("I am flying") class CivilAircraft(FlyableMixin, Vehicle): # 民航飞机 pass class Helicopter(FlyableMixin, Vehicle): # 直升飞机 pass class Car(Vehicle): # 汽车 pass # ps: 采用某种规范(如命名规范)来解决具体的问题是python惯用的套路
可以看到,上面的CivilAircraft、Helicopter类实现了多继承,不过它继承的第一个类我们起名为FlyableMixin,而不是Flyable,这个并不影响功能,但是会告诉后来读代码的人,这个类是一个Mixin类,表示混入(mix-in),这种命名方式就是用来明确地告诉别人(python语言惯用的手法),这个类是作为功能添加到子类中,而不是作为父类,它的作用同Java中的接口。所以从含义上理解,CivilAircraft、Helicopter类都只是一个Vehicle,而不是一个飞行器。
3.3 mixins机制使用注意
使用Mixin类实现多重继承要非常小心
- 首先它必须表示某一种功能,而不是某个物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀
- 其次它必须责任单一,如果有多个功能,那就写多个Mixin类,一个类可以继承多个Mixin,为了保证遵循继承的“is-a”原则,只能继承一个标识其归属含义的父类
- 然后,它不依赖于子类的实现
- 最后,子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。(比如飞机照样可以载客,就是不能飞了)
Mixins是从多个类中重用代码的好方法,但是需要付出相应的代价,我们定义的Minx类越多,子类的代码可读性就会越差,并且更恶心的是,在继承的层级变多时,代码阅读者在定位某一个方法到底在何处调用时会晕头转向,如下
class Displayer: def display(self, message): print(message) class LoggerMixin: def log(self, message, filename='logfile.txt'): with open(filename, 'a') as fh: fh.write(message) def display(self, message): super().display(message) # super的用法请参考下一小节 self.log(message) class MySubClass(LoggerMixin, Displayer): def log(self, message): super().log(message, filename='subclasslog.txt') obj = MySubClass() obj.display("This string will be shown and logged in subclasslog.txt") # 属性查找的发起者是obj,所以会参照类MySubClass的MRO来检索属性 #[<class '__main__.MySubClass'>, <class '__main__.LoggerMixin'>, <class '__main__.Displayer'>, <class 'object'>] # 1、首先会去对象obj的类MySubClass找方法display,没有则去类LoggerMixin中找,找到开始执行代码 # 2、执行LoggerMixin的第一行代码:执行super().display(message),参照MySubClass.mro(),super会去下一个类即类Displayer中找,找到display,开始执行代码,打印消息"This string will be shown and logged in subclasslog.txt" # 3、执行LoggerMixin的第二行代码:self.log(message),self是对象obj,即obj.log(message),属性查找的发起者为obj,所以会按照其类MySubClass.mro(),即MySubClass->LoggerMixin->Displayer->object的顺序查找,
# 在MySubClass中找到方法log,开始执行super().log(message, filename='subclasslog.txt'),super会按照MySubClass.mro()查找下一个类,在类LoggerMixin中找到log方法开始执行,最终将日志写入文件subclasslog.txt
3.4 总结
问:多继承到底要不用?
答:要用,但是规避几点问题
1、继承结构尽量不要过于复杂
2、推荐使用mixins机制:在多继承的背景下满足继承的什么"是"什么的关系
四、派生与方法重用
在子类派生的新方法中如何重用父类的功能?
4.1 子类派生新方法中重用父类功能的两个方式
方法一:“指名道姓”地调用某一个类的函数
# 方式一:指名道姓调用某一个类下的函数=》不依赖于继承关系 class OldboyPeople: 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(OldboyPeople): def __init__(self,name,age,sex,level,salary): OldboyPeople.__init__(self,name,age,sex) self.level = level self.salary=salary tea_obj=Teacher('egon',18,'male',10,3000) print(tea_obj.__dict__)
方法二:super()
调用super()会得到一个特殊的对象,该对象专门用来引用父类的属性,且严格按照MRO规定的顺序向后查找
# 方式二:super()调用父类提供给自己的方法=》严格依赖继承关系 # 调用super()会得到一个特殊的对象,该对象会参照发起属性查找的那个类的mro,去当前类的父类中找属性 class OldboyPeople: 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(OldboyPeople): def __init__(self,name,age,sex,level,salary): # super(Teacher,self).__init__(name,age,sex) Python2中需要传入类名与self,python3中简化为无须传参 super().__init__(name,age,sex) # 调用的是方法,自动传入对象 self.level = level self.salary=salary # print(Teacher.mro()) tea_obj=Teacher('egon',18,'male',10,3000) print(tea_obj.__dict__)
PS:在Python2中super的使用需要完整地写成super(自己的类名,self) ,而在python3中可以简写为super()
4.2 super()的查找顺序
两种方式的区别:方式一是跟继承没有关系的,而方式二的super()是依赖于继承的,并且即使没有直接继承关系,super()仍然会按照MRO继续往后查找
# super()案例 class A: def test(self): print('from A') super().test() class B: def test(self): print('from B') class C(A,B): pass obj=C() obj.test() # from A from B print(C.mro()) # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
obj.test()首先找到A下的test方法,执行super().test()会基于MRO列表(以C.mro()为准)当前所处的位置继续往后查找(),然后在B中找到了test方法并执行。
关于在子类中重用父类功能的这两种方式,使用任何一种都可以,但是在最新的代码中还是推荐使用super()
五、 组合
在一个类中以另外一个类的对象作为数据属性,称为类的组合。组合与继承都是用来解决代码的重用性问题。不同的是:
继承是一种“是”的关系,比如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承;
组合则是一种“有”的关系,比如老师有生日,老师有多门课程,当类之间有显著不同,并且较小的类是较大的类所需要的组件时,应该使用组合,如下示例
class Course: def __init__(self,name,period,price): self.name=name self.period=period self.price=price def tell_info(self): print('<%s %s %s>' %(self.name,self.period,self.price)) class Date: def __init__(self,year,mon,day): self.year=year self.mon=mon self.day=day def tell_birth(self): print('<%s-%s-%s>' %(self.year,self.mon,self.day)) class People: school='清华大学' def __init__(self,name,sex,age): self.name=name self.sex=sex self.age=age #Teacher类基于继承来重用People的代码,基于组合来重用Date类和Course类的代码 class Teacher(People): #老师是人 def __init__(self,name,sex,age,title,year,mon,day): super().__init__(name,age,sex) self.birth=Date(year,mon,day) #老师有生日 self.courses=[] #老师有课程,可以在实例化后,往该列表中添加Course类的对象 def teach(self): print('%s is teaching' %self.name) python=Course('python','3mons',3000.0) linux=Course('linux','5mons',5000.0) teacher1=Teacher('lili','female',28,'博士生导师',1990,3,23) # teacher1有两门课程 teacher1.courses.append(python) teacher1.courses.append(linux) # 重用Date类的功能 teacher1.birth.tell_birth() # 重用Course类的功能 for obj in teacher1.courses: obj.tell_info()
此时对象teacher1集对象独有的属性、Teacher类中的内容、Course类中的内容于一身(都可以访问到),是一个高度整合的产物