继承,派生,组合
Ⅰ面向对象之继承
【一】继承介绍
【1】什么是继承
- 继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类
- 新建的类如果有自己的属性称为派生类或子类。
【2】继承的优点
- 子类会“”遗传”父类的属性,从而解决代码重用问题(去掉冗余的代码)
【3】继承的分类
- python中类的继承分为:单继承和多继承
# 定义父类
class School:
pass
- 单继承:继承一个父类的子类
# 单继承,基类是School,派生类是Student
class Student(School):
pass
# 继承的类叫父类 School
# 新建的类叫子类 Student
- 多继承:继承多个父类的子类
# python支持多继承,用逗号分隔开多个继承的类
class Student(School1, School2):
pass
【4】单继承多继承举例
class Person(object):
height = 180
weight = 50
class School(object):
school = "清华"
# 【1】单继承:只继承一个父类
class Student(Person):
def __init__(self, name):
self.name = name
def tell_me(self):
print(f"我是 {self.name} 我身高 {self.height} 体重 {self.weight}")
stu1 = Student("silence")
stu1.tell_me() # 我是 silence 我身高 180 体重 50
# 【2】多继承:继承两个以上的父类
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("happy")
teacher.tell_me() # 我是 happy 我身高 180 体重 50 学校在 清华
【5】如何查看继承的父类
# (1)查看当前继承的父类 __base__ : 如果继承多个父类,默认只打印第一个继承的父类
print(Student.__base__) # <class '__main__.Person'>
print(Teacher.__base__) # <class '__main__.School'>
# (2)查看当前继承的父类们 : __bases__ 一个元组,元组中放的是所有继承的父类
print(Student.__bases__) # (<class '__main__.Person'>,)
print(Teacher.__bases__) # (<class '__main__.School'>, <class '__main__.Person'>)
【二】经典类和新式类
- 只有在python2中才分新式类和经典类,python3中统一都是新式类
- 在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
- 在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
- 在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
# 定义父类
class School1:
pass
# 定义父类
class School2:
pass
print(School1.__bases__)
# (<class 'object'>,)
print(School2.__bases__)
# (<class 'object'>,)
【三】继承和抽象(先抽象再继承)
- 继承描述的是子类与父类之间的关系,是一种什么是什么的关系。
- 要找出这种关系,必须先抽象再继承
- 抽象即抽取类似或者说比较像的部分,相当于将某部分抽立起来再总结
【1】抽象
-
抽象分成两个层次
-
- 将奥巴马和梅西这俩对象比较像的部分抽取成类;
- 将人,猪,狗这三个类比较像的部分抽取成类。
-
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
【2】继承
- 继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
- 抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
【三】继承和抽象
- 继承是由少变多
- 抽象是由多变少
【四】在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}可以喝水")
# 【3】继承
# 有一只猫
class Cat(Animal):
def __init__(self, name):
self.name = '猫'+name
# 有一只狗
class Dog(Animal):
def __init__(self, name):
self.name = '狗'+name
cat_one = Cat(name='来福')
print(cat_one.speak()) # 猫来福可以叫
print(cat_one.eat()) # 猫来福可以吃饭
print(cat_one.drink()) # 猫来福可以喝水
dog_one = Dog(name="旺财")
print(dog_one.speak()) # 狗旺财可以叫
【四】封装后继承的属性查找顺序
【1】封装之前的属性查找顺序
-
有了继承关系,对象在查找属性时
-
- 先从对象自己的__dict__中找
- 如果没有则去子类中找
- 然后再去父类中找……
class Foo:
def f1(self):
print('Foo.f1')
# 【4】发现父类中找到了 f2 方法
def f2(self):
# 【5】打印 Foo.f2
print('Foo.f2')
# 【6】
# 但是这里犯了难,这个 self 到底是 Foo 还是 Bar ?
# 我们要时刻记得,源头是谁,这个self就是谁
# 我们是从 对象 b 来的,而对象 b 又是 Bar 的对象
# 我们从 Bar 找到了 Foo 类里面,所以我们的源头就是 Bar
# 那这个 self 就是 Bar , 而 Bar 类里面有 f1 方法
# 所以我们就会回到 Bar 类里面
self.f1() # 变为Bar.f1
# 【7】因为是 通过Bar实例化得到的对象,所以 self 就是 Bar
class Bar(Foo):
# 【3】来到 Bar 类里面寻找 f2 方法,但是发现自己没有
# 于是向上查找,去父类 Foo 中查找
def f1(self):
# 【8】 找到了 Bar 中的f1
# 【9】打印 Bar.f1
print('Bar.f1')
#【1】类实例化得到对象
b = Bar()
# 【2】对象调用方法 f2
b.f2()
# 【10】所以打印的顺序是 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:
# __f1变形为_Foo__f1
def __f1(self):
print('Foo.f1')
# 【4】在Foo里面找到了 f2
def f2(self):
# 【5】打印 Foo.f2
print('Foo.f2')
# 【6】
# 没有变形的时候 self 是谁就去谁里面找
# 但是变形之后 就只能在自己的类里面找,没办跳到别的类里面
# 变形为self._Foo__f1,因而只会调用自己所在的类中的方法
self.__f1()
class Bar(Foo):
# 【3】没有f2. 去 Foo
# __f1变形为_Bar__f1
def __f1(self):
print('Bar.f1')
# 【1】
b = Bar()
# 在父类中找到f2方法,进而调用b._Foo__f1()方法,同样是在父类中找到该方法
# 【2】
b.f2()
# Foo.f2
# Foo.f1
【3】总结
- 如果属性不封装的情况下,谁实例化得到的self 就去谁里面找
- 如果属性封装的情况下 , 谁实例化得到的self 无效,只能在当前所在的类的民称空间里面找
【五】继承实现的原理
【1】非菱形结构继承顺序
- 在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如A(B,C,D)
- 如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性
【2】菱形结构继承顺序
- 如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先
(1)深度优先
- 当类是经典类时,多继承情况下,在查找属性不存在时,会按照深度优先的方式查找下去
- 会一条路走到黑 ,最先找到最深处G
(2)广度优先
- 当类是新式类时,多继承情况下,在查找属性不存在时,会按照广度优先的方式查找下去
- 不会一条路走到黑, 先广度寻找 最后全没有才会到深度G
(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__)
# (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
# 新式类继承顺序:F->D->B->E->C->A
# 经典类继承顺序:F->D->B->A->E->C
# python3中统一都是新式类
# pyhon2中才分新式类与经典类
Ⅱ 派生
【一】什么是派生
- 派生是指,子类继承父类,派生出自己的属性与方法,并且重用父类的属性与方法
【二】派生的方法
- 子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找
- 例如每个老师还有职称这一属性
- 我们就需要在Teacher类中定义该类自己的__init__覆盖父类的
- 当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了
【三】子类继承父类的属性
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('silence', 'male', 25, '高玩')
print(obj.name, obj.sex, obj.age, obj.title)
# silence male 25 高玩
【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):
# 直接调用 父类 中 的 __init__ 方法
# 调用的是函数,因而需要传入self
People.__init__(self, name, age, sex)
self.title = title
def teach(self):
print('%s is teaching' % self.name)
obj = Teacher('silence', 'male', 25, '高玩')
print(obj.name, obj.sex, obj.age, obj.title)
# silence male 25 高玩
【2】继承方式二
- 调用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)
# super: 超类 继承父类所有的属性 包括__init__
self.title = title
def teach(self):
print('%s is teaching' % self.name)
obj = Teacher('silence', 'male', 25, '高玩')
print(obj.name, obj.sex, obj.age, obj.title)
# silence male 25 高玩
Ⅲ 组合
【一】什么是组合
- 在一个类中以另外一个类的对象作为数据属性,称为类的组合。
【二】组合的使用
-
组合与继承都是用来解决代码的重用性问题。
-
不同的是:
-
- 继承是一种“是”的关系,比如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承;
- 而组合则是一种“有”的关系,比如老师有生日,老师有多门课程,当类之间有显著不同,并且较小的类是较大的类所需要的组件时,应该使用组合
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('silence', 'male', 25, '高玩', 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】组合的方式
- 用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY