【一】什么是派生
- 派生是指,子类继承父类,派生出自己的属性与方法,并且重用父类的属性与方法
【二】派生的方法
- 子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找
- 例如每个老师还有职称这一属性
- 我们就需要在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('zhang', 'melo', 10, '老师')
print(obj.name, obj.sex, obj.age, obj.title)
# zhang melo 10 老师
- 很明显子类Teacher中
__init__
内的前三行又是在写重复代码
- 若想在子类派生出的方法内重用父类的功能,有两种实现方式
【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('zhang', 'male', 18, '老师')
print(obj.name, obj.sex, obj.age, obj.title)
# zhang 18 male 老师
【2】超类(super())
- 调用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('zhang', 'melo', 10, '老师')
print(obj.name, obj.sex, obj.age, obj.title)
# zhang melo 10 老师
- 提示:在Python2中super的使用需要完整地写成super(自己的类名,self) ,而在python3中可以简写为super()。
- 当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。
- 只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次
- (注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)
【3】小结
- 这两种方式的区别是:
- 方式一是跟继承没有关系的,而方式二的super()是依赖于继承的
- 并且即使没有直接继承关系,super()仍然会按照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
- 此时对象teacher1集对象独有的属性、Teacher类中的内容、Course类中的内容于一身(都可以访问到),是一个高度整合的产物
- 当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
【五】组合和继承的区别
- 组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,
【1】继承的方式
- 通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
- 当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
【2】组合的方式
- 用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...