21. 面向对象之继承
1. 概念
继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类
python中类的继承分为:单继承和多继承
2. 单继承、多继承、查看继承
2.1 单继承
新类只继承一个基类
class Animal:
category = '动物'
def eat(self):
print('动物可以吃东西')
def speak(self):
print('动物可以发出声音')
class Duck(Animal): # 继承了Animal类中的所有方法和属性
category = '鸭子'
def swim(self):
print('鸭子可以游泳')
obj = Duck()
obj.eat() # 动物可以吃东西
print(obj.category) # 鸭子 先在自己的属性字典中找
2.2 多继承、查看继承
新类继承多个基类
class Animal:
category = '动物'
def eat(self):
print('动物可以吃东西')
def speak(self):
print('动物可以发出声音')
class Duck(Animal): # 继承了Animal类中的所有方法和属性
category = '鸭子'
def swim(self):
print('鸭子可以游泳')
class DonaldDuck():
def play(self):
print('Donald喜欢玩')
class SmallDuck(DonaldDuck, Duck):
def eat(self):
print('小鸭子喜欢吃东西')
obj = SmallDuck()
obj.eat() # 小鸭子喜欢吃东西 先从自己的属性字典中找
print(obj.category) # 鸭子 基类DonaldDuck中没有,去基类Duck中找
obj.play() # Donald喜欢玩 去基类DonaldDuck中找
# _ _base_ _查看从左到右继承的第一个基类
# _ _bases_ _查看所有继承的基类
print(SmallDuck._ _bases_ _) # (<class '__main__.DonaldDuck'>, <class '__main__.Duck'>)
print(SmallDuck._ _base_ _) # <class '__main__.DonaldDuck'>
print(Animal._ _bases_ _) # (<class 'object'>,)
print(Animal._ _base_ _) # <class 'object'>
3. 经典类与新式类
只有在python2中才分新式类和经典类,python3中都是新式类
在python2中,没有明显地继承object类,以及该类的子类,都是经典类
在python2中,明显地声明继承object类,以及该类的子类,都是新式类
在python3中,都默认继承object,即python3中所有类均为新式类
如果没有指定基类,python3的类会默认继承object类,object是所有python类的基类,提供了一些常见方法(如_ _ str _ _)的实现
4. 属性的查找顺序
4.1 不隐藏属性
有了继承关系,对象在查找属性时,先从对象自己的属性字典中找,没有则去基类中找
class Teacher:
def f1(self):
print('当前是Teacher的f1')
def f2(self): # (3)在基类中找到了f2方法
print('当前是Teacher的f2')
print(self) # (8)查看self的本质 <__main__.Student object at 0x000001BD9E42B9D0>
# (4)self的本质是一个对象,因此这里的self是stu1对象
# 而stu1对象的属性字典中有f1方法,该方法在产生对象的Student类中
# 因此又回到了Student类中
self.f1()
class Student(Teacher): # (2)来到Student类中找f2方法,发现没有,于是去基类Teacher中查找
def f1(self): # (5)找到了f1方法
print('当前是Student的f1')
stu1 = Student() # 实例化得到一个对象stu1
stu1.f2() # (1)stu1对象调用f2方法
# (6)因此打印的顺序是'当前是Teacher的f2,'当前是Student的f1'
# 当前是Teacher的f2
# 当前是Student的f1
# (7)查看stu1的本质
print(stu1) # <__main__.Student object at 0x000001BD9E42B9D0>
# (9)根据步骤7和步骤8可以得出结论,Teacher类f2函数的self和对象stu1的内存地址一致
4.2 隐藏属性
class Teacher:
def __f1(self): # 封装之后函数名变成_Teacher__f1
print('当前是Teacher的f1')
def f2(self): # (2)发现Student类中没有,于是来到基类Teacher中找到f2
print('当前是Teacher的f2')
# print(self) # (6)查看self的本质<__main__.Student object at 0x000002BA5A73B9D0>
self.__f1() # (3)本质是stu1._Teacher__f1()
# (4)发现对象stu1的属性字典中并没有_Teacher__f1,于是只能在类Teacher中找_Teacher__f1
# print(stu1.__dict__) # {}
# print(Teacher.__dict__) # '_Teacher__f1': <function Teacher.__f1 at 0x00000216C9D13370>, 'f2': <function Teacher.f2 at 0x00000216C9FE1750>,
class Student(Teacher):
def __f1(self): # 封装之后函数名变成_Student__f1
print('当前是Student的f1')
stu1 = Student() # 实例化得到一个对象stu1
stu1.f2() # (1)stu1对象调用f2方法
# (5)因此打印的顺序是'当前是Teacher的f1',当前是Teacher的f2
# 当前是Teacher的f1'
# 当前是Teacher的f2
# (7)查看stu1的本质
# print(stu1) # <__main__.Student object at 0x000002BA5A73B9D0>
# (8)根据步骤6和步骤7可以得出结论,Teacher类f2函数的self和对象stu1的内存地址一致
4.3 总结
在没有隐藏属性的情况下,属性的查找顺序是:
对象属性字典---实例化得到该对象的类---基类---再上一级基类
在隐藏属性的情况下,属性的查找顺序是:
从当前执行该方法的类中去找封装属性---找不到就报错
5. 继承与抽象
抽象:抽取类似或者比较像的部分
哈兰德、基利安—— 人
杰瑞、泰菲—— 鼠
斯派克、泰克—— 狗
人、鼠、狗—— 动物
继承:基于抽象的结果,得到这些比较像的部分
动物—— 人、鼠、狗
人—— 哈兰德、基利安
鼠—— 杰瑞、泰菲
狗—— 斯派克、泰克
6. 菱形继承的属性查找
6.1 概念
菱形继承属性查找方式有两种,分别是深度优先和广度优先,区分条件是新式类和经典类。
6.2 代码
class A(object):
def test(self):
print('from A')
class B(A):
def test(self):
print('from B')
class C(A):
def test(self):
print('from C')
class D(B):
def test(self):
print('from D')
class E(C):
def test(self):
print('from E')
class F(D,E):
pass
obj = F()
obj.test() # from D
6.3 经典类:深度优先
深度优先,先不分开去找,先一个方向找到最顶层基类,如果没找到则从另一个方向找
F--D--B--A E--C
6.4 新式类:广度优先
广度优先,先不找最顶层的基类,一个方向找不到去另一个方向找,最后找顶层基类
F--D--B E--C--A
6.5 查看类的查找顺序
_ _mro_ _ 可以查看类的查找顺序,但是这个方法只对新式类有效
class A(object):
def test(self):
print('from A')
class B(A):
def test(self):
print('from B')
class C(A):
def test(self):
print('from C')
class D(B):
def test(self):
print('from D')
class E(C):
def test(self):
print('from E')
class F(D,E):
pass
print(F.__mro__)
# (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
print(F.mro())
# [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
7. 派生
7.1 概念
派生是指子类在继承基类的属性后,衍生出自己的属性,并且重写基类的属性
7.2 引入
class Person:
school = 'MIT'
def __init__(self, name, age, grade):
self.name = name
self.age = age
self.grade = grade
def lab(self):
print('实验')
class Student(Person):
def __init__(self, course):
self.course = course
def lab(self):
print('学生正在做实验')
stu1 = Student(course='控制工程')
print(stu1.course) # 控制工程
print(stu1.school) # MIT
stu1.lab() # 学生正在做实验
# 找不到grade,原因是在子类中又重写了基类_ _ init _ _的方法,导致了原本的init方法失效
print(stu1.grade) # AttributeError: 'Student' object has no attribute 'grade'
7.3 子类派生属性中继承基类属性的两种方法
方法一:调用基类的函数
class Person:
school = 'MIT'
def __init__(self, name, age, grade):
self.name = name
self.age = age
self.grade = grade
def lab(self):
print('实验')
class Student(Person):
def __init__(self, course, name, age, grade):
Person.__init__(self, name, age, grade) # 子类从基类中继承属性
self.course = course
def lab(self):
print('学生正在做实验')
stu1 = Student(name='lavigne', age=20, grade=2, course='control')
print(stu1.course) # control
print(stu1.school) # MIT
方法二:使用内置函数super
调用super()会得到一个特殊的对象,该对象专门用来引用基类的属性,且严格按照MRO规定的顺序向后查找
class Person:
school = 'MIT'
def __init__(self, name, age, grade):
self.name = name
self.age = age
self.grade = grade
def lab(self):
print('实验')
class Society:
def __init__(self, job):
self.job = job
def vocation(self, where):
print(f'在{where}度假')
class Student(Society, Person):
def __init__(self, course, job):
super().__init__(job) # super只能继承第一个基类,如果继承第二个基类的属性则会报错
self.course = course
def holiday(self, where):
super().vocation(where) # 继承基类的函数属性
stu1 = Student(course='控制工程', job='学生')
print(stu1.course) # 控制工程
# 使用super加载基类中的属性
print(stu1.job) # 学生
stu1.holiday(where='黄金海岸') # 在黄金海岸度假
class Society:
def __init__(self, job):
self.job = job
def vocation(self, where):
print(f'在{where}度假')
class Student(Society):
def __init__(self, course, work):
super().__init__(work) # super只能继承第一个基类,如果继承第二个基类的属性则会报错
self.course = course
stu1 = Student(course='控制工程', work='学生')
# 使用super加载基类中的属性
print(stu1.job) # 学生 类Student里面的work只是个中间参数,最终的值是类Society里面的job
方法三:方法一与方法二的组合
class Person:
school = 'MIT'
def __init__(self, name, age, grade):
self.name = name
self.age = age
self.grade = grade
def lab(self):
print('实验')
class Society:
def __init__(self, job):
self.job = job
def vocation(self, where):
print(f'在{where}度假')
class Student(Society, Person):
def __init__(self, course, job, name, age, grade):
Person.__init__(self, name, age, grade)
super().__init__(job) # super只能继承第一个基类,如果继承第二个基类的属性则会报错
self.course = course
def holiday(self, where):
super().vocation(where) # 继承基类的函数属性
stu1 = Student(course='Music', job='学生', name='lavigne', age=20, grade=2)
print(stu1.course) # 控制工程
# 查看基类中的属性
print(stu1.job) # 学生
print(stu1.name) # lavigne
print(stu1.age) # 20
stu1.holiday(where='黄金海岸') # 在黄金海岸度假
总结:
方式一注意调用函数的时候要传入self,方式二super函数继承的是第一个基类
8. 组合
在一个类中,以另外一个类的对象作为数据属性,称为类的组合
class Course:
def __init__(self, kind, teacher):
self.kind = kind
self.teacher = teacher
def show_info(self):
print(f'课程是{self.kind},教师是{self.teacher}')
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def show_birthday(self):
return f'生日是{self.year}年{self.month}月{self.day}'
class Person:
school = 'MIT'
def __init__(self, name, age, grade):
self.name = name
self.age = age
self.grade = grade
class Student:
def __init__(self, name, age, grade, year, month, day):
Person.__init__(self, name, age, grade)
self.birthday = Date(year, month, day) # 生日信息,构建了一个对象
self.courses = [] # 课程信息
def stu_info(self):
print(f'学生是{self.name},年龄是{self.age},年级是{self.grade}'
f'生日是{self.birthday.show_birthday()}')
stu1 = Student(name='ronaldo', age=20, grade=1, year=2000, month='Jan', day=1)
music = Course(kind='music', teacher='lavigne')
art = Course(kind='art', teacher='avril')
stu1.courses.append(music) # 参数是一个对象
stu1.courses.append(art) # 参数是一个对象
stu1.stu_info() # 学生是ronaldo,年龄是20,年级是1生日是生日是2000年Jan月1
print(stu1.birthday.show_birthday()) # 生日是2000年Jan月1
stu1.courses[0].show_info() # 课程是music,教师是lavigne
继承:子类继承了父类的数据和方法,衍生出自己的数据和方法
组合:将多个类组合到一起,组合后的类拥有多个类的数据和方法
9. 抽象类
9.1 概念
抽象类是一个特殊的类,只能被继承,不能被实例化
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子,你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于,抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法
9.2 不重写抽象类方法
from abc import ABC, abstractmethod
class Animal(ABC):
def __init__(self, name):
self.name = name
@abstractmethod
def run(self):
pass
@abstractmethod
def speak(self):
pass
def eat(self):
pass
class Dog(Animal):
def __init__(self, name):
super().__init__(name)
dog1 = Dog('小黑') # 报错,子类没有定义抽象方法
# TypeError: Can't instantiate abstract class Dog with abstract methods run, speak
9.3 重写抽象类方法
from abc import ABC, abstractmethod
class Animal(ABC):
def __init__(self, name):
self.name = name
@abstractmethod # 定义抽象方法,无需实现功能
def run(self):
pass
@abstractmethod # 定义抽象方法,无需实现功能
def eat(self):
pass
def speak(self):
pass
class Dog(Animal):
def __init__(self, name):
super().__init__(name)
def run(self): # 子类继承抽象类,必须定义抽象方法
print('跑起来了')
def eat(self):
print('进食')
dog1 = Dog('小黑')
dog1.run() # 跑起来了
dog1.eat() # 进食