继承与派生
一、继承
-
继承是一种新建类的方式,新建的类称之为子类或派生类,继承的父类称之为基类或超类
在python中,一个子类可以继承多个父类
在其他语言中,一个子类只能继承一个父类
-
继承的作用
- 减少代码的冗余
-
如何实现继承
- 先确认谁是子类,谁是父类
- 在定义子类时,子类名(父类名)
class Father1:
pass
class Father2:
pass
class Father3:
pass
# 子类
class Sub(Father1, Father2, Father3):
pass
# 子类 .__bases__ 查看父类
print(Sub.__bases__)
print(Sub.x)
二、 如何寻找继承关系
- 确认谁是子类
- 对象——>子类——>父类
- 得先抽象,再继承
- 抽取对象之间相似的部分,总结出类
- 抽取类之间相似的部分,总结出父类
# 问题: 代码冗余
# 老师类
class OldboyTeacher:
school = 'oldboy'
country = 'China'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
# 老师修改分数
def change_score(self):
print ('老师%s正在修改分数...' % self.name)
#学生类
class OldboyStudent:
school = 'oldboy'
country = 'China'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
# 学生选择课程
def choose_course(self):
print ('学生%s正在选择课程' % self.name)
stu1 = OldboyStudent('maomao', 18, '男')
print (stu1.school, stu1.name, stu1.age, stu1.sex)
tea1 = OldboyTeacher('tank', 28, '男')
print (tea1.school, tea1.name, tea1.age, tea1.sex)
上面的代码看起来冗余很多,所有有了以下的解决办法
# 解决代码冗余问题
# 老男孩人类
class OldboyPeople:
school = 'oldboy'
country = 'China'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
# 老师类
class OldboyTeacher(OldboyPeople):
# 老师修改分数
def change_score(self):
print (f'老师{self.name}正在修改分数...')
# 学生类
class OldboyStudent(OldboyPeople):
# 学生选择课程
def choose_course(self):
print (f'学生{self.name}正在选择课程...')
stu1 = OldboyStudent('maomao', 18, '男')
print (stu1.school, stu1.name, stu1.age, stu1.sex)
tea1 = OldboyTeacher('tank', 28, '男')
print(tea1.school, tea1.name, tea1.age, tea1.sex)
三、在继承背景下对对象属性的查找顺序
注意:程序的执行顺序是由上到下,所以父类必须定义在子类的上方
- 在继承背景下,对象属性的查找顺序:
- 先从对象自己的名称空间中查找
- 对象中没有,从子类的名称空间中查找
- 子类中没有,从父类的名称空间中查找,若父类没有,则会报错
# 父类
class Goo:
x = 10
# 子类
class Foo(Goo):
x = 100
foo_obj = Foo()
四、 派生
派生:指的是紫萼里继承父类的属性和方法,并且派生出自己独有的属性与方法
如果子类中的方法名与父类的相同,优先使用子类的
# 父类
class Foo:
def f1(self):
print ('from FOO.f1...')
def f2(self):
print ('from Foo.f2...')
# 子类
class Bar(Foo):
def f1(self):
print ('from Bar.f1...')
def func(self):
print ('from Bar.func...')
# 派生后继承关系查找验证
bar_obj = Bar()
bar_obj.f2()
# 结果
from Foo.f2...
from Bar.f1...
五、重用
- 子类继承父类,派生出自己的属性和方法,并且重用父类的属性与方法
问题就是: 子类重写父类中的__init__
导致代码更加冗余
解决问题:子类重用父类的属性,并派生出新的属性
- 两种解决方式
- 直接应用父类的
__init__
为其传参,并添加子类的属性 - 通过super来指向父类的属性
- super() 是一个特殊的类,调用super得到一个对象,该对象指向父类的名称空间
- 直接应用父类的
注意:使用哪一种都是可以的,但是不可以两种混合使用
# 方式二
class OldboyPeople:
school = 'oldboy'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
class OldboyTeacher(OldboyPeople):
def __init__(self, name, age, sex, sal):
# super()——>特殊的对象——>对象.属性——>父类的名称空间
# 会将调用类传入的对象当作第一个参数传给__init__()
super().__init__(name, age, sex)
self.sal = sal
def change_score(self):
print (f'老师{self.name}正在修改分数...')
class OldboyStudent(OldboyPeople):
def __init__(self, name, age, sex, home):
super().__init__(name, age, sex)
self.home = home
def choose_course(self):
print (f'学生{self.name}正在选择课程...')
tea1 = OldboyTeacher('tank', 18, 'male', 20000)
print (tea1.name,tea1.age,tea1.sex,tea1.sal)
stu1 = OldboyStudent('maomao', 18, 'female', '上海市')
print (stu1.name,stu1.age,stu1.sex,stu1.home)
super的方式就可以避免很多的冗余代码,每个子类下都不需要再重新写self
六、 经典类与新式类
- 新式类
- 凡是继承object的类或子孙都是新式类
- 在python3中所有的类都默认继承object
- 经典类
- 在python2中才会有经典类与新式类之分
- 在python2中,凡是没有继承object的类,都是经典类
class User(object):
pass
class User:
x = 10
pass
class Sub(User):
pass
七、mro继承顺序
调用mro返回的是一个继承序列
super的继承顺序严格遵循mro继承序列
class Father1:
x = 10
class Father2:
x = 20
# 多继承的情况下,从左到右
class Sub(Father1, Father2):
def __init__(self):
print (super().__delattr__)
print (Sub.mro())
obj = Sub()
print (object)
python3中提供了一个查找新式类查找顺序的内置方法
mro():会把当前类的继承关系列出来
# 注意:super()会严格按照mro列表的顺序往后查找
class A:
def test(self):
print ('from A test')
super().test()
class B:
def test(self):
print ('from B.test')
class C(A,B):
pass
c =C()
# 检查super的继承顺序
print (C.mro())
# 去A找,有的话打印,然后super又执行了test,根据mro中查找打印B类中的test
八、 钻石继承(菱形继承)
多继承的情况下,造成“钻石继承”
- mro的查找顺序
- 新式类
- 广度优先
- 经典类
- 深度优先
- 新式类