【一】继承介绍
-
继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。
-
子类会“遗传”父类的属性,从而解决代码重用问题(去掉冗余的代码)
-
继承:
- 单继承:继承一个父类的子类
- 多继承:继承多个父类的子类
class Student(School):
# 继承的类叫父类 School
# 新建的类叫子类 Student
【1】单继承和多继承
# 定义父类
class ParentClass1:
pass
# 定义父类
class ParentClass2:
pass
# 单继承,基类是ParentClass1,派生类是SubClass
class SubClass1(ParentClass1):
pass
# python支持多继承,用逗号分隔开多个继承的类
class SubClass2(ParentClass1, ParentClass2):
pass
class School(object):
school = '清华'
class Person(object):
height = 180
weight = 50
# 单继承
class Student(Person):
def __init__(self, name):
self.name = name
def tell_me(self):
print(f"我是 {self.name} 我身高 {self.height} 体重 {self.weight}")
stu1 = Student("chosen")
stu1.tell_me() # 我是 chosen 我身高 180 体重 50
# 多继承
class Teacher(School, Person):
def __init__(self, name):
self.name = name
def tell_me(self):
print(f"我是 {self.name} 我身高 {self.height} 体重 {self.weight} 学校在{self.school}")
teacher = Teacher("max")
teacher.tell_me() # 我是 max 我身高 180 体重 50 学校在清华
【2】查看继承
# 查看继承的父类
print(Student.__base__) # <class '__main__.Person'>
print(Teacher.__base__) # <class '__main__.School'>
# 查看当前继承的父类们
print(Student.__bases__) # (<class '__main__.Person'>,)
print(Teacher.__bases__) # (<class '__main__.School'>, <class '__main__.Person'>)
【二】经典类和新式类
- 只有在python2中才分新式类和经典类,python3中统一都是新式类
- 在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
【1】什么是经典类
- 在 py2 中没有显示继承 object 的类或者是该类的子类都是经典类
【2】什么是新式类
- 在 py2 中显示继承 object 的类或者是该类的子类都是新式类
- 在 py3 之后所有的类默认都是新式类,不写默认继承 object
【三】继承和抽象(先抽象再继承)
- 继承描述的是子类与父类之间的关系,是一种什么是什么的关系。
- 抽象即抽取类似或者说比较像的部分
【1】抽象
- 抽象分成两个层次
- 将奥巴马和梅西这俩对象比较像的部分抽取成类;
- 将人,猪,狗这三个类比较像的部分抽取成父类。
- 抽象最主要的作用是划分类别
【2】继承
- 继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
- 抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
【3】继承和抽象
- 继承是由少变多
- 抽象是由多变少
【4】在python中实现继承
[1]继承总类
- 猫可以:喵喵叫、吃、喝、拉、撒
- 狗可以:汪汪叫、吃、喝、拉、撒
# 有一只猫
class Cat(object):
def speak(self):
print(f"猫可以喵喵叫")
def eat(self):
print("猫可以吃饭")
def drink(self):
print("猫可以喝水")
# 有一只狗
class Dog(object):
def speak(self):
print(f"狗可以旺旺叫")
def eat(self):
print("狗可以吃饭")
def drink(self):
print("狗可以喝水")
[2]代码实现
class Animal(object):
def speak(self):
print(f"{self.name}可以叫")
def eat(self):
print(f"{self.name}可以吃饭")
def drink(self):
print(f"{self.name}可以喝水")
class Dog(Animal):
def __init__(self, name):
self.name = '狗' + name
class Cat(Animal):
def __init__(self, name):
self.name = '猫' + name
cat_one = Cat(name='小花')
cat_one.speak() # 猫小花可以叫
dog_one = Dog(name='旺财')
dog_one.speak() # 狗旺财可以叫
【四】封装后继承的属性查找顺序
【1】封装之前的属性查找顺序
- 有了继承关系,对象在查找属性时
- 先从对象自己的
__dict__
中找 - 如果没有则去子类中找
- 然后再去父类中找……
- 先从对象自己的
class Foo:
def f1(self):
print('Foo.f1')
# 【3】发现父类中找到了 f2 方法
def f2(self):
print('Foo.f2')
# 【4】
# 但是这里犯了难,这个 self 到底是 Foo 还是 Bar ?
# 我们要时刻记得,源头是谁,这个self就是谁
# 我们是从 对象 b 来的,而对象 b 又是 Bar 的对象
# 我们从 Bar 找到了 Foo 类里面,所以我们的源头就是 Bar
# 那这个 self 就是 Bar , 而 Bar 类里面有 f1 方法
# 所以我们就会回到 Bar 类里面
self.f1()
class Bar(Foo):
# 【2】来到 Bar 类里面寻找 f2 方法,但是发现自己没有
# 于是向上查找,去父类 Foo 中查找
def f1(self):
# 【5】 找到了 Bar 中的方法
print('Bar.f1')
# 实例化类得到对象
b = Bar()
# 【1】对象调用方法
b.f2()
# 【6】所以打印的顺序是 Foo 类中的 f2 , Bar 类中的 f1
# Foo.f2
# Bar.f1
- b.f2()会在父类Foo中找到f2
- 先打印Foo.f2
- 然后执行到self.f1(),即b.f1()
- 仍会按照:对象本身->类Bar->父类Foo的顺序依次找下去
- 在类Bar中找到f1
- 因而打印结果为Foo.f1
【2】有封装的时候的继承
- 父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的方式将方法设置为私有的
class Foo:
# 变形为_Foo__fa
def __f1(self):
print('Foo.f1')
def f2(self):
print('Foo.f2')
# 变形为self._Foo__fa,因而只会调用自己所在的类中的方法
self.__f1()
class Bar(Foo):
# 变形为_Bar__f1
def __f1(self):
print('Bar.f1')
b = Bar()
# 在父类中找到f2方法,进而调用b._Foo__f1()方法,同样是在父类中找到该方法
b.f2()
# Foo.f2
# Foo.f1
【3】总结
- 如果属性不封装的情况下,谁实例化得到的 self 就去谁里面找
- 如果属性封装的情况下,谁实例化得到的 self 无效,只能在当前所在的类的名称空间里面找
【五】派生
- 派生是指子类继承父类,派生出自己的属性与方法,并且重用父类的属性与方法
【1】子类继承父类的属性
# 子类继承父类的属性
class People:
school = '清华大学'
def __init__(self,name,sex,age):
self.name = name
self.sex = sex
self.age = age
class Teacher(People):
# 派生 : 派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找
def __init__(self,name,sex,age,title):
self.name = name
self.sex = sex
self.age = age
self.title = title
def teach(self):
print('%s is teaching' % self.name)
# 只会找自己类中的__init__,并不会自动调用父类的
obj = Teacher('chosen', 'male', 20, '高级讲师')
print(obj.name, obj.sex, obj.age, obj.title)
# chosen male 20 高级讲师
【2】继承方式一
- 指名道姓的调用某一个类的函数
class People:
school = '清华大学'
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
class Teacher(People):
# 派生 : 派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找
def __init__(self, name, sex, age, title):
# 直接调用 父类 中 的 __init__ 方法
# 调用的是函数,因而需要传入self
People.__init__(self, name, age, sex)
self.title = title
def teach(self):
print('%s is teaching' % self.name)
# 只会找自己类中的__init__,并不会自动调用父类的
obj = Teacher('chosen', 'male', 20, '高级讲师')
print(obj.name, obj.sex, obj.age, obj.title)
# # chosen male 20 高级讲师
【3】继承方式二
- 调用super()会得到一个特殊的对象
- 该对象专门用来引用父类的属性
- 且严格按照MRO规定的顺序向后查找
class People:
school = '清华大学'
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
class Teacher(People):
# 派生 : 派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找
def __init__(self, name, sex, age, title):
# 直接调用 父类 中 的 __init__ 方法
# 调用的是绑定方法,因此会自动传入self,但是需要传入相应的参数
super().__init__(name, sex, age)
self.title = title
def teach(self):
print('%s is teaching' % self.name)
# 只会找自己类中的__init__,并不会自动调用父类的
obj = Teacher('chosen', 'male', 18, '高级讲师')
print(obj.name, obj.sex, obj.age, obj.title)
# chosen male 18 高级讲师
【六】继承实现的原理
【1】非菱形结构继承的顺序
- 在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如A(B,C,D)
- 如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性
【2】菱形结构继承顺序
- 如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先(当前 py3 版本实行广度优先)
[1]深度优先
- 当类是经典类时,多继承情况下,在查找属性不存在时,会按照深度优先的方式查找下去
[2]广度优先
- 当类是新式类时,多继承情况下,在查找属性不存在时,会按照广度优先的方式查找下去
【3】代码
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):
# def test(self):
# print('from F')
pass
f1 = F()
f1.test()
# 只有新式才有这个属性可以查看线性列表,经典类没有这个属性
print(F.__mro__)
# 新式类继承顺序:F->D->B->E->C->A
# 经典类继承顺序:F->D->B->A->E->C
# python3中统一都是新式类
# pyhon2中才分新式类与经典类
- 为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
- 我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类
【七】组合
- 在一个类中以另一个类的对象作为数据属性,称为类的组合
class Course:
def __init__(self, name, period, price):
self.name = name
self.period = period
self.price = price
def tell_info(self):
print(f'当前课程名字 {self.name} 当前课程周期 {self.period} 当前课程价格 {self.price}')
class Date:
def __init__(self, year, mon, day):
self.year = year
self.mon = mon
self.day = day
def tell_birth(self):
print(f'当前生日 {self.year} 年 {self.mon} 月 {self.day} 日')
class People:
school = '清华大学'
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
# Teacher类基于继承来重用People的代码
# 基于组合来重用Date类和Course类的代码
class Teacher(People):
# 老师是人
def __init__(self, name, sex, age, title, year, mon, day):
super().__init__(name, age, sex)
# 老师有生日
self.birth = Date(year, mon, day)
# 老师有课程,可以在实例化后,往该列表中添加Course类的对象
self.courses = []
def teach(self):
print(f'当前老师正在授课 {self.name}')
python = Course('python', '3mons', 3000.0)
linux = Course('linux', '5mons', 5000.0)
teacher1 = Teacher('dream', 'male', 18, '金牌讲师', 1987, 3, 23)
# teacher1有两门课程
teacher1.courses.append(python)
teacher1.courses.append(linux)
# 重用Date类的功能
teacher1.birth.tell_birth()
# 重用Course类的功能
for obj in teacher1.courses:
obj.tell_info()
# 当前生日 1987 年 3 月 23 日
# 当前课程名字 python 当前课程周期 3mons 当前课程价格 3000.0
# 当前课程名字 linux 当前课程周期 5mons 当前课程价格 5000.0
【1】组合和继承的区别
- 组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,
【2】继承的方式
- 通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
- 当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
【3】组合的方式
- 用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...