一、继承的概念算法
继承是一种新建类的方式,新建类能够建立一个或多个父类,父类又能够称之为基类或超类,新建的类称之为子类或派生类,继承描述的是一种遗传关系,子类能够重用父类的属性和方法。使用继承能够减小类与类之间代码冗余的问题。编程
在 Python 中类的继承分为单继承和多继承编程语言
class ParentClass1(): # 定义父类
pass
class ParentClass2(): # 定义父类
pass
class SubClass1(ParentClass1): # 定义子类继承父类, 单继承
pass
class SubClass2(ParentClass1, ParentClass2): # Python支持多继承, 用逗号分隔开多个继承的类
pass
二、查看全部继承的父类:__bases__ide
class ParentClass1(): pass
class ParentClass2(): pass
class SubClass1(ParentClass1): pass
class SubClass2(ParentClass1, ParentClass2): pass
print(SubClass1.__bases__) print(SubClass2.__bases__) print(ParentClass1.__bases__) print(ParentClass2.__bases__) # 运行结果
(<class '__main__.ParentClass1'>,) (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>) (<class 'object'>,) (<class 'object'>,)
若是没有指定父类,Python3 会默认继承 object 类,object 是全部类的父类函数
新式类:但凡继承 object 类的子类,以及该子类的子子类....,都称之为新式类spa
经典类:没有继承 object 类的子类,以及该子类的子子类....,都称之为经典类code
只有在 Python2 中才区分新式类和经典类,Python3 所有默认是新式类对象
三、继承与抽象(先抽象后继承)blog
抽象:抽取类似的部分(也就是提取一类事物的特色,范围愈来愈大,共性愈来愈少),是从大范围到小范围的过程继承
继承:是基于抽象的过程,经过编程语言去实现它,确定是先经历抽象这个过程,才能经过继承的方式去表达出抽象的结构,是从小范围到大范围的过程
四、派生
1)在父类的基础上产生子类,产生的子类就叫作派生类
2)父类中没有的方法,在子类中有,这样的方法就叫作派生方法
3)父类中有个方法,子类中也有这个方法,就叫作方法的重写(就是把父类里的方法重写了)
五、注意的几个概念
1)子类可使用父类的全部属性和方法
2)若是子类有本身的方法,就执行本身的;若是子类没有本身的方法,就会找父类的
3)若是子类里面没有找到,父类里也没有找到,就会报错
六、在子类派生出新的功能中如何重用父类的功能
如今我定义一个学生类,隶属于学校 “湫兮如风” ,学生有姓名,年龄,性别,还能够选课;再建立教师类,也隶属于学校 “湫兮如风” ,教师有姓名,年龄,性别,等级,工资,教师能够给学生打分
class Student(): school = '湫兮如风学院'
def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def choose_course(self): print('%s正在选课' %self.name) class Teacher(): school = '湫兮如风学院'
def __init__(self, name, age, gender, level, salary): self.name = name self.age = age self.gender = gender self.level = level self.salary = salary def score(self, stu, num): print('教师%s给学生%s打%s分' %(self.name, stu.name, num)) stu.num = num
可是这样写有不少重复的代码,分析一下条件,教师和学生都属于学院的人,因而能够定义出一个父类让教师和学生去继承这个父类
class People(): school = '湫兮如风学院'
class Student(People): def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def choose_course(self): print('%s正在选课' %self.name) class Teacher(People): def __init__(self, name, age, gender, level, salary): self.name = name self.age = age self.gender = gender self.level = level self.salary = salary def score(self, stu, num): print('教师%s给学生%s打%s分' %(self.name, stu.name, num)) stu.num = num
这样的处理还不够完全,由于还有 __init__ 里面还有部分重复的代码,能够将这些重复的代码放入父类,可是教师类中还有等级和工资没法处理,这两个对于父类来讲就是多出来的,会报错
class People(): school = '湫兮如风学院'
def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Student(People): def choose_course(self): print('%s正在选课' %self.name) class Teacher(People): def score(self, stu, num): print('教师%s给学生%s打%s分' %(self.name, stu.name, num)) stu.num = num stu = Student('qiu', 22, 'male') tea = Teacher('xi', 20, 'male', 10, 3000) # 运行
TypeError: __init__() takes 4 positional arguments but 6 were given
能够在教师类中派生新的 __init__ ,将教师的姓名,年龄,性别,等级,工资写入,但这又回到了上一步,又有了重复的代码,这就引入了须要解决的问题:在子类派生出新的功能中如何重用父类的功能?
在函数中咱们介绍了调用的概念,在父类中有这个功能,在子类中不想再写,就能够调用这个功能
class People(): school = '湫兮如风学院'
def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Student(People): def choose_course(self): print('%s正在选课' %self.name) class Teacher(People): def __init__(self, name, age, gender, level, salary): # 注意: 这里不能使用self去调用__init__, 这样调用的永远是本身的__init__,
# 由于这里的self是Teacher的一个对象, 就是tea, tea先从本身去找__init__, 没有找到
# 再到Teacher中找__init__, 找到了, 因而永远在调用本身的__init__, 没法访问到父类的__init__
# 因而用类名去调, 这实际上就是一个普通函数, 没有自动传值的功能, 须要将参数一个很多的传入
People.__init__(self, name, age, gender) self.level = level self.salary = salary def score(self, stu, num): print('教师%s给学生%s打%s分' %(self.name, stu.name, num)) stu.num = num stu = Student('qiu', 22, 'male') tea = Teacher('xi', 20, 'male', 10, 3000)
这是第一种方式,指名道姓的访问某一个类中的函数,这实际上与面向对象无关,即使是 Teacher 和 Person 没有继承关系,也可以调用
在讲第二种方式以前,须要先了解在继承背景下,经典类与新式类的属性查找顺序
首先是单继承,在单继承背景下,不管是新式类仍是经典类属性查找顺序都同样,都是先从对象中查找,对象中没有到类中,类中没有再到父类中。
而在多继承背景下,若是一个子类继承多个父类,但这些父类没有再继承同一个非 object 的父类,在这种状况下,新式类仍是经典类属性查找顺序也是同样的,先在对象中查找,对象中没有再到对象所在的类中,再没有就会按照从左到右的顺序一个父类一个父类的找下去
因此这里的查找顺序是:obj ---> A ---> B ---> E ---> C ---> F ---> D,D中没有再到 object 中查找,在这里 object 不在讨论范围以内,好比要查找 name 属性,object类 中不可能会去定义一个 name 属性。下面是代码实现
class E(): x = 'E'
pass
class F(): X = 'F'
pass
class B(E): # x = 'B'
pass
class C(F): x = 'C'
pass
class D(): x = 'D'
pass
class A(B, C, D): # x = 'A'
pass obj = A() print(obj.x)
还有一种状况,被称之为 “菱形继承” ,能够抽象的想像到,最终的父类是汇聚到一点,继承了同一个父类,而且这个父类也不是 object 类。在这种状况下,新式类和经典类的属性查找顺序不一样。
在新式类中,是按照广度优先查找,即:obj ---> A ---> B ---> E ---> C ---> F ---> D ---> G
在经典类中,是按照深度优先查找,即:obj ---> A ---> B ---> E ---> G ---> C ---> F ---> D
对于定义的每个类,Python 会计算出一个方法解析顺序(MRO)列表,这个 MRO 列表就是用于保存继承顺序的一个列表
class G(): x = 'G'
pass
class E(G): x = 'E'
pass
class F(G): X = 'F'
pass
class B(E): x = 'B'
pass
class C(F): x = 'C'
pass
class D(G): x = 'D'
pass
class A(B, C, D): x = 'A'
pass obj = A() # print(obj.x)
print(A.mro()) # 运行结果
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]
为了实现继承,Python 会在 MRO 列表上从左到右开始查找父类,直到找到第一个匹配这个属性的类为止,而这个 MRO 列表的构造是经过一个 C3 线性化算法来实现的。咱们不去深究这个算法的数学原理,它实际上就是合并全部父类的 MRO 列表并遵循以下三条准则:
1)子类会先于父类被检查
2)若是有多个父类,则根据继承语法在列表内的书写顺序被检查
3)若是多个类继承了同一个父类,则子类中只会选取继承语法括号中的第一个父类
了解了以上知识点,如今继续来讲在子类派生出的新功能中如何重用父类功能的方式二
有一个叫作 super 的内置函数,调用该函数会获得一个特殊的对象,该对象是专门用来访问父类中属性和方法
class People(): school = '湫兮如风学院'
def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Student(People): def choose_course(self): print('%s正在选课' %self.name) class Teacher(People): def __init__(self, name, age, gender, level, salary): super(Teacher, self).__init__(name, age, gender) self.level = level self.salary = salary def score(self, stu, num): print('教师%s给学生%s打%s分' %(self.name, stu.name, num)) stu.num = num stu = Student('qiu', 22, 'male') tea = Teacher('xi', 20, 'male', 10, 3000)
强调:super 会严格参照类的 MRO 列表依次查找属性
这是子类在类里面调用父类方法,使用 super(子类类名,self).方法名() 或 super().__init__(参数),在 Python3 中 super 能够不传参数,Python2 中必需要传参数