面向对象之继承
1.什么是继承?
继承是一种新建类的方式,新建的类称之为子类或派生类,继承的父类称之为基类或超类。
在Python中,一个子类可以继承多个父类。(面试可能会问)
在其它语言中,一个子类只能继承一个父类。
2.继承的作用?
减少代码的冗余。
3.如何实现继承?
-
先确认谁是子类,谁是父类。
-
在定义类子类时, 通过class 子类名(父类名):这种方式就可以继承父类的属性和方法。可以通过 :子类名.__ base __查看到父类是谁
# 父类
class Father1:
x = 1
pass
class Father2:
pass
class Father3:
pass
# 子类,
class Sub(Father1, Father2, Father3):
pass
# 子类.__bases__ 查看父类
print(Sub.__bases__)
print(Sub.x)
4.如何寻找继承关系,即如何确定父类子类
得先抽象,再继承
- 抽取对象之间相似的部分,总结出类
- 抽取类之间相似的部分,总结出父类。
# 父类 # 总结出所有子类的相似部分
class OldboyPeople:
school = 'oldboy'
country = 'China'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
# 老师类
class OldboyTeacher(OldboyPeople):
# school = 'oldboy' # 相似的属性和方法可以抽取出来定义成父类
# country = 'China'
# def __init__(self, name, age, sex):
# self.name = name
# self.age = age
# self.sex = sex
# 老师修改分数
def change_score(self):
print(f'老师 {self.name} 正在修改分数...')
# 学生类
class OldboyStudent(OldboyPeople):
# school = 'oldboy'
# country = 'China'
#
# def __init__(self, name, age, sex):
# self.name = name
# self.age = age
# self.sex = sex
# 学生选择课程
def choose_course(self):
print(f'学生 {self.name} 正在选择课程...')
5.继承背景下对象属性的查找顺序
- 1.先从对象自己的名称空间中查找
- 2.对象中没有,从子类的名称空间中查找。
- 3.子类中没有, 从父类的名称空间中查找,若父类没有,则会报错!
# 父类
class Goo:
x = 10
pass
# 子类
class Foo(Goo):
x = 100
pass
foo_obj = Foo()
foo_obj.x = 1000 # 注意,这不是修改子类的属性,而是在子类对象里面添加了新属性
print(foo_obj.x)
print('对象的名称空间: ', foo_obj.__dict__)
print('子类的名称空间: ', Foo.__dict__)
print('父类的名称空间: ', Goo.__dict__)
6.什么叫派生
派生: 指的是子类继承父类的属性与方法,并且派生出自己独有的属性与方法。 若子类中的方法名与父类的相同,优先用子类的。
# 父类
class Foo:
def f1(self):
print('from Foo.f1...')
def f2(self): # self ---> bar_obj
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.f1() # 输出结果:from Bar.f1..
bar_obj.func() # 输出结果:from Bar.func...
bar_obj.f2() # 输出结果:from Foo.f2...
# 查找方法的顺序也是先在对象中找,如果对象中没有,则会在子类中找如果子类中没有,则去父类中找,如果父类中没有,则报错
问题: 子类重写父类的__init__导致代码更加冗余
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):#想派生出自己的属性,还需要重新写父类的属性,这样非常麻烦
self.name = name
self.age = age
self.sex = sex
self.sal = sal
def change_score(self):
print(f'老师 {self.name} 修改分数...')
class OldboyStudent(OldboyPeople):
def __init__(self, name, age, sex, girl):
self.name = name
self.age = age
self.sex = sex
self.girl = girl
def choose_course(self):
print(f'学生 {self.name} 选择课程...')
tea1 = OldboyTeacher('tank', 17, 'male', 15000000)
stu1 = OldboyStudent('sean', 28, 'male', '凤姐')
print(tea1.name, tea1.age, tea1.sex, tea1.sal)
print(stu1.name, stu1.age, stu1.sex, stu1.girl)
'''
解决问题: 子类重用父类的属性,并派生出新的属性。
两种方式:
1.直接引用父类的__init__为其传参,并添加子类的属性。
2.通过super来指向父类的属性。
- super()是一个特殊的类,调用super得到一个对象,该对象指向父类的名称空间。
注意: 使用哪一种都可以,但不能两种方式混合使用。
'''
方式一:
class OldboyPeople:
school = 'oldboy'
def __init__(self, name, age, sex): # self == tea1, name == 'tank', age == 17, sex == 'male'
self.name = name
self.age = age
self.sex = sex
class OldboyTeacher(OldboyPeople):
# tea1, 'tank', 17, 'male', 15000000
def __init__(self, name, age, sex, sal):
# self.name = name
# self.age = age
# self.sex = sex
# 类调用类内部的__init__,只是一个普通函数
# OldboyPeople.__init__(tea1, 'tank', 17, 'male')
OldboyPeople.__init__(self, name, age, sex) # 在子类的.__init__中调用
self.sal = sal # 这样只需要写添加的属性就可以了
def change_score(self):
print(f'老师 {self.name} 修改分数...')
class OldboyStudent(OldboyPeople):
def __init__(self, name, age, sex, girl):
OldboyPeople.__init__(self, name, age, sex)
self.girl = girl
def choose_course(self):
print(f'学生 {self.name} 选择课程...')
tea1 = OldboyTeacher('tank', 17, 'male', 15000000) # 传参的时候别忘了
print(tea1.name, tea1.age, tea1.sex, tea1.sal)
stu1 = OldboyStudent('sean', 28, 'male', '凤姐')
print(stu1.name, stu1.age, stu1.sex, stu1.girl)
# 方式二:
class OldboyPeople:
school = 'oldboy'
# self == tea1
def __init__(self, name, age, sex): # self == tea1, name == 'tank', age == 17, sex == 'male'
self.name = name
self.age = age
self.sex = sex
class OldboyTeacher(OldboyPeople):
# tea1, 'tank', 17, 'male', 15000000
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, girl):
super().__init__(name, age, sex)
self.girl = girl
def choose_course(self):
print(f'学生 {self.name} 选择课程...')
tea1 = OldboyTeacher('tank', 17, 'male', 15000000)
print(tea1.name, tea1.age, tea1.sex, tea1.sal)
stu1 = OldboyStudent('sean', 28, 'male', '凤姐')
print(stu1.name, stu1.age, stu1.sex, stu1.girl)
7.经典类和新式类(了解)
-
新式类:
1.凡是继承object的类或子孙类都是新式类。
2.在python3中所有的类都默认继承object。
-
经典类:
1.在python2中才会有经典类与新式类之分。
2.在python2中,凡是没有继承object的类,都是经典类。
class User(object): # 新式类
pass
class Sub(User):
pass
print(User.__dict__)
#输出结果:
{'__module__': '__main__', 'x': 10, '__dict__': <attribute '__dict__' of 'User' objects>, '__weakref__': <attribute '__weakref__' of 'User' objects>, '__doc__': None}
print(object)
输出结果:
<class 'object'>
'''
在python3中提供了一个查找新式类查找顺序的内置方法.
mro():类的内置方法,会把当前类的继承关系列出来。
'''
# 注意: super()会严格按照mro列表的顺序往后查找,mro列表的构造是通过c3线性算法来实现的
class A:
def test(self):
print('from A.test')
super().est()
class B:
def est(self):
print('from B.test')
class C(A, B):
pass
c = C()
# 检查super的继承顺序
print(C.mro())
# 输出结果:是一个继承顺序的列表
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
# 去A找,有的话打印,然后super又执行了test,根据mro中查找打印B类中test。
c.test()
'''
from A.test
from B.test
'''
多继承情况下造成 “钻石继承” 或“菱形继承”
mro的查找顺序:
-
新式类:
广度优先
-
经典类:
深度优先