Python基础之继承与派生
一、什么是继承:
继承是一种创建新的类的方式,新建的类可以继承一个或过个父类,原始类成为基类或超类,新建的类则称为派生类
或子类。
其中,继承又分为:单继承和多继承。
class parent_class1: #定义父类(基类或超类) pass class parent_class2: #定义父类(基类或超类) pass class subclass1(parent_class1): #单继承,父类(基类或超类)是:parent_class1, pass # 子类(派生类)是:subclass。 class subclass2(parent_class1,parent_class2): #支持多继承,括号里的父类用逗号隔开 pass
注意:圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在
子类中未找到时,从左到右查找父类中是否包含方法。
查看继承:
print(subclass1.__bases__) print(subclass2.__bases__) ------------------输出结果-------------------------- (<class '__main__.parent_class1'>,) (<class '__main__.parent_class1'>, <class '__main__.parent_class2'>)
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方
法(如__str__)的实现。
print(parent_class1.__bases__) print(parent_class2.__bases__) --------------输出结果----------------- (<class 'object'>,) #默认继承的父类 (<class 'object'>,) #默认继承的父类
二、继承与抽象(先抽象后继承)
抽象:抽取类似的或者比较像的部分,就是找出共同点。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到我们想要的类
抽象分为两步:
1、先将奔驰和宝马这两对象比较像的部分抽取成类;
2、再将轿车、货车、摩托车这三个类,比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象
的结构。
三、继承与重用性
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同
时,我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用。
class motor_vehicle: #父类:机动车 tag = "fuel oil" #共有的特征都必须烧燃油 def __init__(self,brand,motorcycle_type,displacement): self.brand = brand #车辆品牌 self.motorcycle_type = motorcycle_type #车辆类型 self.displacement = displacement #车辆排量 def advance(self): #都有前进的技能 print("%s,出发了!"%self.brand) def stop(self,other): #都有刹车技能 print("%s,遇到开 %s 的帅哥减速了!"%(self.brand,other.brand)) class saloon(motor_vehicle): #轿车类继承父类(机动车) pass class motorcycle(motor_vehicle): #摩托车类继承父类(机动车) pass s1 = saloon("奔驰","saloon",600) #实例化对象s1 m1 = motorcycle("春风","motorcycle",650) #实例化对象m1 print(s1.displacement) #查看s1的排量 print(m1.brand) #查看m1的品牌 s1.stop(m1) #对象调用对应的绑定方法。 -------------------输出结果--------------------------- 600 春风 奔驰,遇到开 春风 的帅哥减速了!
提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的代码,大大增加了编程的工作量,这就是常说
的代码重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件
开发周期,对大型软件开发来说,意义重大.
注意:像m1.brand之类的属性引用,会先从实例中找brand然后去类中找,然后再去父类中找...直到最顶级的父类。
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新
定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
class motorcycle(motor_vehicle): #摩托车类继承父类(机动车) def advance(self):#在自己这里定义新的advance,不再使用父类的advance,且不会影响父类 print("from motorcycle") def fun(self): #定义新的功能 print("michael is a motorcycle trip") ---------------输出结果------------------- from motorcycle michael is a motorcycle trip
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是
用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值了。
class motorcycle(motor_vehicle): #摩托车类继承父类(机动车) def __init__(self,brand,motorcycle_type,displacement,owner): motor_vehicle.__init__(self,brand,motorcycle_type,displacement) self.owner = owner #增加新的属性 def advance(self):#在自己这里定义新的advance,不再使用父类的advance,且不会影响父类 print("from motorcycle") def fun(self): #定义新的功能 print("%s is a motorcycle trip"%self.owner) print("------下面是调用父类中的功能-------") motor_vehicle.stop(m1,m1) #调用父类中的功能 m1 = motorcycle("春风","motorcycle",650,"michael") #实例化对象m1 print(m1.owner) #查看新增的属性春风机车的车主是谁 m1.advance() #调用m1的绑定方法 m1.fun() #调用m1的绑定方法,其中有调用父类中的功能 ------------------------输出结果-------------------------- michael from motorcycle michael is a motorcycle trip ------下面是调用父类中的功能------- 春风,遇到开 春风 的帅哥减速了!
四、组合与重用性
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
比如咱们现在要去摩旅,得需要一些必要的装备吧,简单点,就先来一个导航仪(GPS)吧!
#定义装备类 class equipment: #摩旅需要的装备 def gps(self): #定义GPS这个技能 print("gps全程为您服务!") #定义摩托车类 class motorcycle(motor_vehicle): #摩托车摩旅都需要装备,因而需要组合equipment类 def __init__(self,brand,motorcycle_type,displacement): motor_vehicle.__init__(self,brand,motorcycle_type,displacement) self.equipment = equipment() # 用equipment类产生一个装备,赋值给实例的equipment属性 def advance(self):#在自己这里定义新的advance,不再使用父类的advance,且不会影响父类 print("from motorcycle") def fun(self): #定义新的功能 print("michael is a motorcycle trip") m1 = motorcycle("春风","motorcycle",650) #实例化对象m1 m1.equipment.gps() #可以使用组合的类产生的对象所持有的方法 ----------------输出结果------------------ gps全程为您服务!
注意:组合与继承都是有效地利用已有类的资源的重要方式,但是二者的概念和使用场景皆不同。
1、继承的方式
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如奔驰是轿车,货车车是机动车。当类之间有很
多相同的功能,提取这些共同的功能做成基类,用继承比较好。
比如摩托车是机动车:
class motor_vehicle: #父类:机动车 tag = "fuel oil" #共有的特征都必须烧燃油 def __init__(self,brand,motorcycle_type,displacement): self.brand = brand #车辆品牌 self.motorcycle_type = motorcycle_type #车辆类型 self.displacement = displacement #车辆排量 def advance(self): #都有前进的技能 print("%s,出发了!"%self.brand) class motorcycle(motor_vehicle): #摩托车是机动车 pass m1 = motorcycle("春风","motorcycle",650) #实例化对象m1 m1.advance() #调用父类中对应的绑定方法 -------------------输出结果-------------------- 春风,出发了!
2、组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系。
比如摩托车有装备:
#定义装备类 class equipment: #摩旅需要的装备 def gps(self): #定义GPS这个技能 print("gps全程为您服务!") #定义摩托车类 class motorcycle(motor_vehicle): #摩托车摩旅都需要装备,因而需要组合equipment类 def __init__(self,brand,motorcycle_type,displacement): motor_vehicle.__init__(self,brand,motorcycle_type,displacement) self.equipment = equipment() # 用equipment类产生一个装备,赋值给实例的equipment属性 def advance(self):#在自己这里定义新的advance,不再使用父类的advance,且不会影响父类 print("from motorcycle") def fun(self): #定义新的功能 print("michael is a motorcycle trip") m1 = motorcycle("春风","motorcycle",650) #实例化对象m1 m1.equipment.gps() #可以使用组合的类产生的对象所持有的方法 ----------------输出结果------------------ gps全程为您服务!
注意:当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好。
五、接口与归一化设计
1、什么是接口
1 =================第一部分:Java 语言中的接口很好的展现了接口的含义: IAnimal.java 2 /* 3 * Java的Interface很好的体现了我们前面分析的接口的特征: 4 * 1)是一组功能的集合,而不是一个功能 5 * 2)接口的功能用于交互,所有的功能都是public,即别的对象可操作 6 * 3)接口只定义函数,但不涉及函数实现 7 * 4)这些功能是相关的,都是动物相关的功能,但光合作用就不适宜放到IAnimal里面了 */ 8 9 package com.oo.demo; 10 public interface IAnimal { 11 public void eat(); 12 public void run(); 13 public void sleep(); 14 public void speak(); 15 } 16 17 =================第二部分:Pig.java:猪”的类设计,实现了IAnnimal接口 18 package com.oo.demo; 19 public class Pig implements IAnimal{ //如下每个函数都需要详细实现 20 public void eat(){ 21 System.out.println("Pig like to eat grass"); 22 } 23 24 public void run(){ 25 System.out.println("Pig run: front legs, back legs"); 26 } 27 28 public void sleep(){ 29 System.out.println("Pig sleep 16 hours every day"); 30 } 31 32 public void speak(){ 33 System.out.println("Pig can not speak"); } 34 } 35 36 =================第三部分:Person2.java 37 /* 38 *实现了IAnimal的“人”,有几点说明一下: 39 * 1)同样都实现了IAnimal的接口,但“人”和“猪”的实现不一样,为了避免太多代码导致影响阅读,这里的代码简化成一行,但输出的内容不一样,实际项目中同一接口的同一功能点,不同的类实现完全不一样 40 * 2)这里同样是“人”这个类,但和前面介绍类时给的类“Person”完全不一样,这是因为同样的逻辑概念,在不同的应用场景下,具备的属性和功能是完全不一样的 */ 41 42 package com.oo.demo; 43 public class Person2 implements IAnimal { 44 public void eat(){ 45 System.out.println("Person like to eat meat"); 46 } 47 48 public void run(){ 49 System.out.println("Person run: left leg, right leg"); 50 } 51 52 public void sleep(){ 53 System.out.println("Person sleep 8 hours every dat"); 54 } 55 56 public void speak(){ 57 System.out.println("Hellow world, I am a person"); 58 } 59 } 60 61 =================第四部分:Tester03.java 62 package com.oo.demo; 63 64 public class Tester03 { 65 public static void main(String[] args) { 66 System.out.println("===This is a person==="); 67 IAnimal person = new Person2(); 68 person.eat(); 69 person.run(); 70 person.sleep(); 71 person.speak(); 72 73 System.out.println("\n===This is a pig==="); 74 IAnimal pig = new Pig(); 75 pig.eat(); 76 pig.run(); 77 pig.sleep(); 78 pig.speak(); 79 } 80 } 81 82 java中的interface
2、继承有两种用途:
a、继承基类的方法,并且做出自己的改变或者扩展(代码重用)
b、声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并
未实现接口的功能,子类继承接口类,并且实现接口中的功能
class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。 def read(self): #定接口函数read pass def write(self): #定义接口函数write pass class Txt(Interface): #文本,具体实现read和write def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(Interface): #磁盘,具体实现read和write def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') class Process(All_file): def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法')
实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,
可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所
有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设
备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用
,子类完全可以不用去实现接口 ,如果非要去模仿接口的概念,可以借助第三方模块。
3、为何要用接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。然后让子类去实现接口中的函数。这么做的意义
在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上
来说都一样。
归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使
用者的使用难度。
比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的
类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道
他俩都会跑,都会吃,都能呼吸。
再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类
,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们
都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样。
六、抽象类
1、什么是抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于
只能被继承,不能被实例化。
抽象类:本质还是类,与普通类额外的特点的是:加了装饰器的函数,子类必须实现他们。
2、为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括
数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时
,要么是吃一个具体的香蕉,要么是吃一个具体的桃子……你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例
化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案。
3、在Python中实现抽象类
#一切皆文件 import abc #利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定义抽象方法,无需实现功能 def read(self): '子类必须定义读功能' pass @abc.abstractmethod #定义抽象方法,无需实现功能 def write(self): '子类必须定义写功能' pass # class Txt(All_file): # pass # # t1=Txt() #报错,子类没有定义抽象方法 class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') class Process(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法') wenbenwenjian=Txt() yingpanwenjian=Sata() jinchengwenjian=Process() #这样大家都是被归一化了,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read() print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type)
4、抽象类与接口
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),
而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计。
七、继承实现的原理
1、继承的顺序
1、Python的类是可以继承多个类的,在Java和C#中则只能继承一个类;
2、Python的类如果继承了多个类时,在Py2里寻找父类的方式有两种,分别是:深度优先和广度优先。
在Py3里寻找父类的方式只有一种,即广度优先。
1、当类是经典类时,多继承情况下,会按照深度优先的方式寻找;
2、当类时新式类时,多继承情况下,会按照广度优先的方式寻找。
2、经典类和新式类的区别:
经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了更多的功能,也是之后推荐的写法,从写
法上区分的话,如果 当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。
#继承顺序 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') class D(B): def test(self): print('from D') pass class E(C): def test(self): print('from E') pass class F(D,E): # def test(self): # print('from F') pass f1=F() #实例化对象f1 f1.test() #调用f1的绑定方法,当子类没有的时候,观察依次查找的顺序 print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性 #新式类继承顺序:F->D->B->E->C->A #经典类继承顺序:F->D->B->A->E->C #python3中统一都是新式类 #pyhon2中才分新式类与经典类
3、继承原理(Python如何实现的继承)
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个
MRO列表就是一个简单的所有基类的线性顺序列表。
print((F.__mro__)) #这种方法得到的是一个元组的数据类型 print(F.mro()) #这种方法得到的是一个列表的数据类型,二者内容是一样的 ----------------------输出结果------------------------------- (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。而这个
MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父
类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查;
2.多个父类会根据它们在列表中的顺序被检查;
3.如果对下一个类存在两个合法的选择,选择第一个父类。
八、子类中调用父类方法
子类继承了父类的方法,然后想进行修改,注意了是基于原有的基础上修改,那么就需要在子类中调用父类的
方法。
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()
并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super
调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)。
#每个类中都继承了且重写了父类的方法 class A: def __init__(self): print('A的构造方法') class B(A): def __init__(self): print('B的构造方法') super(B,self).__init__() class C(A): def __init__(self): print('C的构造方法') super(C,self).__init__() class D(B,C): def __init__(self): print('D的构造方法') super(D,self).__init__() print(D.__mro__) #python2中没有这个属性 ---------------输出结果------------------ (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)