Python入门之面向对象之类继承与派生
本章内容
一、继承
二、抽象类
三、继承的实现原理
=======================================================
一、继承
1. 继承的定义
继承是一种新建类的方式,新建的类被称为子类,子类会继承父类的属性。
在Python中支持,一个子类(派生类——可以继承一个或者多个父类(基类或者超类)
2. 为什么要用继承
继承可以有效减少代码冗余
3. 如何使用继承
继承代码示例
# 继承示例 class Father1: pass class Father2: pass class Son1(Father1): # Son1继承Father1 pass class Son2(Father1,Father2): # Son2同时继承Father1 & Father2 pass print(Son1.__bases__) print(Son2.__bases__) print(Father1.__bases__) print(Father2.__bases__)
以上代码的结果如下:
(<class '__main__.Father1'>,) #Son1继承Father1 (<class '__main__.Father1'>, <class '__main__.Father2'>) #Son2继承Father1,Father2 (<class 'object'>,) #Python3中有默认父类object (<class 'object'>,) #Python3中有默认父类object
# 在python3新建的类,默认都有一个父类(object) # 在python2中,默认是没有父类,可以添加(object)为父类
需要注意Python2和Python3中关于类的分类是不一样的:
#Python2中的类: # # 1.经典类 # 指的是没有继承默认父类object的类,以及没有继承object类的子类 # # 2.新式类 # 值得是继承默认父类object以及object类子类的类 # # #Python3中的类: # # 统一都是新式类
4. 建立继承关系
继承是类与类之间的关系,需要寻找这种关系,先进行抽象,然后建立继承
子类所共有的特征,由父类进行统一配置管理
class SchoolPeople: school = 'SH high school' def __init__(self,name,age,gender): self.name = name self.age = age self.gender = gender def tell_info(self): print('School:Name is %s, Age is %s, Gender is %s' %(name,age,gender)) class SchoolStudent(SchoolPeople): #继承SchoolPeople def learn(self): print('%s is learning' %name) def tell_info(self): print('I'm student %s, Age is %s, Gender is %s ' %(name,age,gender)) class SchoolTeacher(schoolPeople): #继承SchoolPeople def teach(self): print('%s is teaching' %name) def tell_info(self): print('I'm teacher %s, Age is %s, Gender is %s ' %(name,age,gender)) student1 = SchoolStudent('Bob','22','Male') teacher1 = SchoolTeacher('Ajax','33','Female') print(student1.__dict__) print(student1.school) print(teacher1.__dict__) #子类已经自己定义tell_info()函数,显示格式将按照子类的格式显示,不需要按照父类的格式 student1.tell_info() teacher1.tell_info()
请仔细查看如下代码:
class Foo: def F1(self): print('Foo.F1') def F2(self): print('Foo.F2') self.F1() class Bar(Foo): #继承Foo def F1(self): #子类中同时也有与父类同名的方法F1 print('Bar.F1') obj = Bar() obj.F2() # obj先到父类Foo中找到F2方法运行 # 运行到self.F1()的时候会调用子类Bar自己的F1()方法,而不在调用父类的F1()方法
以上显示的结果为:
Foo.F2
Bar.F1
5. 子类派生出新的方法中重用父类的功能
派生,是指子类定义自己新的属性,如果与父类同名,以子类自己的为准
a. 调用父类方法,指名道姓的使用,请看如下代码
class SchoolPeople: school = 'oldboy' def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def tell_info(self): print(""" ===========个人信息========== 姓名:%s 年龄:%s 性别:%s """ %(self.name,self.age,self.sex)) class SchoolTeacher(SchoolPeople): # 继承SchoolPeople def __init__(self, name, age, sex, level, salary): # self.name = name # self.age = age # self.sex = sex SchoolPeople.__init__(self,name, age, sex) # 点名指向SchoolPeople self.level = level self.salary = salary def tell_info(self): SchoolPeople.tell_info(self) # 点名指向SchoolPeople print(""" 等级:%s 薪资:%s """ %(self.level,self.salary)) tea1 = SchoolTeacher('Bob', 18, 'male', 9, 50k) # print(tea1.name, tea1.age, tea1.sex, tea1.level, tea1.salary) tea1.tell_info()
b. 调用父类方法,使用super()方法
注意区分Python2和Python3中的super()的格式
这种方法只能调用父类的方法
class SchoolPeople: school = 'oldboy' def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def tell_info(self): print(""" ===========个人信息========== 姓名:%s 年龄:%s 性别:%s """ %(self.name,self.age,self.sex)) class SchoolTeacher(SchoolPeople): # 继承SchoolPeople def __init__(self, name, age, sex, level, salary): # self.name = name # self.age = age # self.sex = sex super().__init__(self,name, age, sex) # 使用super()指向父类,Python3的格式 #super(SchoolPeople,self).__init__(self,name, age, sex) Python2的格式 self.level = level self.salary = salary def tell_info(self): super().tell_info(self) # 使用super()指向父类,Python3的格式 #super(SchoolPeople,self).tell_info(self) Python2的格式 print(""" 等级:%s 薪资:%s """ %(self.level,self.salary)) tea1 = SchoolTeacher('Bob', 18, 'male', 9, 50k) # print(tea1.name, tea1.age, tea1.sex, tea1.level, tea1.salary) tea1.tell_info()
二、抽象类
主要使用abc模块,实现归一化,父类有的方法,子类必须有。
需要在父类的方法头部加上@abc.abstractclassmethod
class File(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): #必须要定义下面的方法,如果没有就不让实例化 pass @abc.abstractmethod def write(self): pass class Sata(File): def read(self): pass def write(self): pass def asb(self): print('adsf') Fil=Sata() Fil.asb() print(Sata.mro())
三、继承的实现原理
1、继承顺序:
经典类:当类是经典类时,多继承情况下,在要查找属性不存在时,会按照深度优先的方式查找下去
新式类:当类是新式类时,多继承情况下,在要查询属性不存在时,会按照广度优先的方式查找下去
2、方法解析顺序(Method Resolution Order, MRO)
在多重继承中存在不相关的祖先类实现同名方法引起的冲突问题,这种问题称作“菱形问题”。
Python依靠特定的顺序遍历继承图,这个顺序叫做方法解析顺序。
如图,左图是类的UML图,右图中的虚线箭头是方法解析顺序:
3、使用属性来查看继承的顺序:
print(类名.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(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() print(F.mro())
4. 处理多重继承的建议
(1)把接口继承和实现继承区分开;
-
-
继承接口:创建子类型,是框架的支柱;
-
继承实现:通过重用避免代码重复,通常可以换用组合和委托模式。
-
(2)使用抽象基类显式表示接口;
(3)通过混入重用代码;
混入类为多个不相关的子类提供方法实现,便于重用,但不会实例化。并且具体类不能只继承混入类。
(4)在名称中明确指明混入;
Python中没有把类声明为混入的正规方式,Luciano推荐在名称中加入Mixin后缀。如Tkinter中的XView应变成XViewMixin。
(5)抽象基类可以作为混入,反过来则不成立;
抽象基类与混入的异同:
-
-
抽象基类会定义类型,混入做不到;
-
抽象基类可以作为其他类的唯一基类,混入做不到;
-
抽象基类实现的具体方法只能与抽象基类及其超类中的方法协作,混入没有这个局限。
-
(6)不要子类化多个具体类;
具体类可以没有,或者至多一个具体超类。
例如,Class Dish(China,Japan,Tofu)中,如果Tofu是具体类,那么China和Japan必须是抽象基类或混入。
(7)为用户提供聚合类;
聚合类是指一个类的结构主要继承自混入,自身没有添加结构或行为。Tkinter采纳了此条建议。
(8)优先使用对象组合,而不是类继承。
优先使用组合可以令设计更灵活。
组合和委托可以代替混入,但不能取代接口继承去定义类型层次结构。