19、面向对象(继承)
19.1、继承介绍:
1、什么是继承:
继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,
新建的类称为派生类或子类。子类会“遗传”父类的属性,从而解决代码重用问题;
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同
时我们不可能从头开始写一个类B,这就用到了类的继承的概念。通过继承的方式新建类B,让B继承A,B会‘遗传’
A的所有属性(数据属性和函数属性),实现代码的重用;
2、python中类的继承分类:
(1)定义父类:
class ParentClass1:
pass
class ParentClass2:
pass
(2)单继承:
class SubClass1(ParentClass1):
pass
# 单继承,父类是ParentClass1,子类是SubClass;
(3)多继承:
class SubClass2(ParentClass1,ParentClass2):
pass
# python支持多继承,用逗号分隔开多个继承的类;
3、经典类与新式类:
(1)只有在python2中才分新式类和经典类,python3中统一都是新式类
(2)在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
(3)在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
(4)在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
4、继承的顺序:
(1)在 Java 和 C# 中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如 A(B,C,D)
如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条
分支的顺序直到找到我们想要的属性,如果继承关系为菱形结构,那么属性的查找方式有两种,分
别是:深度优先和广度优先;
(2)经典类(深度优先):
(3)新式类(广度优先):
5、继承原理(python如何实现的继承):
(1)python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单
的所有基类的线性顺序列表。
例如:>>> F.mro() 就等同于 F.__mro__,得到的结果如下:
(<class '__main__.F'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
(2)为了实现继承,python 会在 MRO 列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。而这个 MRO 列表的构造是通过一个 C3 线性化
算法来实现的,我们不去深究这个算法的数学原理,它实际上就是合并所有父类的 MRO 列表并遵循如下三条准则:
1)子类会先于父类被检查;
2)多个父类会根据它们在列表中的顺序被检查;
3)如果对下一个类存在两个合法的选择,选择第一个父类;
19.2、继承示例:
class Dad:
# 父类
money=1000000
def __init__(self,name):
print('father_class')
self.name=name
def hit_son(self):
print('%s 正在打儿子' %self.name)
class Son(Dad):
# 子类
money = 10000000
def __init__(self,name,age):
self.name=name
self.age=age
def hit_son(self):
print('son_class')
# print(Dad.__dict__)
# print(Son.__dict__)
s1=Son('son',22)
s1.hit_son()
print(s1.money)
print(Dad.money)
# son_class
# 10000000
# 1000000
# 子类继承父类后拥有父类的数据属性和函数属性,使用子类生成
# 对象后调用子类的数据属性和函数属性,如果在子类中找不到回到父类中
# 寻找(若果子类中的数据属性或函数属性和父类相同则优先使用
# 子类中的)
19.3、类的继承顺序示例:
1、继承类图:
2、新式类继承顺序:
class A:
def test(self):
print('A')
pass
class B(A):
def test(self):
print('B')
pass
class C(A):
def test(self):
print('C')
pass
class D(B):
def test(self):
print('D')
pass
class E(C):
def test(self):
print('E')
pass
class F(E,D):
def test(self):
print('F')
pass
f1=F()
f1.test()
print(F.__mro__)
# 该方法只有新式类才有
#新式类:F->E->C->D->B->A-object
2、经典类继承顺序:
经典类:F->E->C->A->D->B
19.4、接口继承:
1、什么是接口:
java中用 interface 关键字来声明接口类;
例如:hi boy,给我开个查询接口。。。此时的接口指的是:自己提供给使用者来调用自己功能的方式\方法\入口;
2、为何要用接口:
(1)接口提取了一群类共同的函数,可以把接口当做一个函数的集合,然后让子类去实现接口中的函数。
(2)使用接口的意义在于归一化,归一化就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使
用时,从用法上来说都一样。
(3)归一化的好处在于:
1)归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使
用者的使用难度。
2)归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合;
3)举例:
比如:linux的泛文件概念,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对
底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,
他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都
会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样。
(4)在python中根本就没有一个叫做 interface 的关键字,如果非要去模仿接口的概念,也可以使用继承:
1)继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是
有害的。因为它使得子类与基类出现强耦合。
2)声明某个子类兼容于某基类,定义一个接口类(模仿java的Interface),接口类中定义了一些接口名(就是函数名)
且并未实现接口的功能,子类继承接口类,并且实现接口中的功能;
3)在 python 中使用继承其实并没有起到接口的作用,子类完全可以不用去实现接口 ,这就用到了抽象类。
3、使用抽象类实现接口功能:
(1)什么是抽象类:
与java一样,python也有抽象类的概念,但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被
继承,不能被实例化;
(2)为什么要用抽象类:
1)说明:
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的
香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
2)从设计角度去看:如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性;
3)从实现角度来看:抽象类与普通类的不同之处在于,抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,
且子类必须实现抽象方法,这一点与接口有点类似,但其实是不同的;
(3)抽象类与接口:
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口之间的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计,可以理解接口是一个特殊的抽象类;
(4)抽象类实现接口功能示例:
import abc
class All_file(metaclass=abc.ABCMeta):
@abc.abstractmethod
def read(self):
pass
@abc.abstractmethod
def write(self):
pass
class Disk(All_file):
def read(self):
print('disk read')
def write(self):
print('disk write')
class Cdrom(All_file):
def read(self):
print('cdrom read')
def write(self):
print('cdrom write')
class Mem(All_file):
def read(self):
print('mem read')
def write(self):
print('mem write')
m1=Mem()
m1.read()
m1.write()
# mem read
# mem write
19.5、子类中调用父类中的方法:
1、示例:
class Vehicle:
Country='China'
def __init__(self,name,speed,load,power):
self.name=name
self.speed=speed
self.load=load
self.power=power
def run(self):
print('开动啦')
class Subway(Vehicle):
def __init__(self,name,speed,load,power,line):
# Vehicle.__init__(self,name,speed,load,power)
# super().__init__(name,speed,load,power)
# super(__class__,self).__init__(name,speed,load,power)
super(Subway,self).__init__(name,speed,load,power)
self.line=line
def show_info(self):
print(self.name,self.speed,self.load,self.power,self.line)
def run(self):
# Vehicle.run(self)
super(Subway,self).run()
print('%s %s 线,开动啦' %(self.name,self.line))
line13=Subway('北京地铁','10km/s',1000000000,'电',13)
line13.show_info()
line13.run()
print(line13.__class__)
# 北京地铁 10km/s 1000000000 电 13
# 开动啦
# 北京地铁 13 线,开动啦
# <class '__main__.Subway'>
# 当你使用 super() 函数时,Python 会在 MRO 列表上继续搜索下一个类,只要每个重定义的方法统一使用 super() 并只调用它一次,
# 那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次;
# 注意:使用 super 调用的所有属性都是从 MRO 列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表;