前言:
在我们了解面向对象基础以后,需要进一步拓展这方面的内容,接着开始了Python面向对象的进阶部分,我们可以使用更少的代码,完成更多的事情。这一篇主要介绍Python3中类的3大特性:继承、封装、多态
,其中之一:继承
类的继承
首先来说一下什么继承:
1、继承是一种新建类的方式,新建类称为:子类(派生类),被继承的类称为:父类(基类)
2、继承的特性:子类会遗传父类的属性和方法
3、继承是类与类之间的关系
使用继承的好处是什么?减少代码冗余,避免了多个了定义多个类的属性或方法相同
通过实例演示未继承前的类:
class Teacher:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Student:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
teacher = Teacher('tom',28,'male')
student = Student('jack',18,'male')
可以看到,它们都需要相同的属性(姓名、年龄、性别),但是未了解继承前只能逐个创建
我们可以创建一个父类出来,用于帮助我们创建相同属性
# 此时,如果哪个类需要父类这些属性,那么可以通过继承来完成
class People:
# 用于接收子类传递的对象与值,这个self代表了调用子类(Teacher 或者 Student)的对象
def __init__(self,name,age,sex):
self.name = name # 拿到传递过来的值以后添加到对象属性里面
self.age = age
self.sex = sex
# 我们通过类名后面的括号里面放于一个类,用于继承
class Teacher(People):
def __init__(self,name,age,sex):
# 将我们调用时传递的对象与值全部传递给父类
People.__init__(self,name,age,sex)
class Student(People):
def __init__(self,name,age,sex):
People.__init__(self,name,age,sex)
teacher = Teacher('tom',28,'male')
student = Student('jack',18,'male')
print(teacher.name)
print(student.name)
执行效果
'tom'
'jack'
可以看到,我们将值传递的类里面去以后,这个类再次将我们对象本身以及值传递给了父类,由父类来给我们对象传递属性!这是继承的好处之一。
我们还可以在父类里面创建方法,父类里面的方法可以提供给所有继承它的子类
class People:
def walk(self):
print('走路')
def speak(self):
print('说话')
def reflection(self):
print('思考')
我们创建子类,使用父类提供的方法
class Student(People):
def f1(self):
# 调用父类里面的方法,因为是通过类名调用,里面的方法等于普通函数,有几个参数就要传递几个
People.walk(self)
People.speak(self)
s = Student()
s.f1()
执行效果
'走路'
'说话'
类的组合搭配
指的是一个对象具备某一个属性,该属性指向的是其它对象,在上一篇也用到过,将多个对象作为属性放入一个对象里。
实例:
# 人的属性(作为父类使用)
class People(object):
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
# 教师类,继承人的属性
class Teacher(People):
def __init__(self,name,age,sex):
super().__init__(name,age,sex)
# 修改学生课程分数,需要传递学生进来,obj(代表学生)
def change(self,obj,curr,score):
# 因为学生有一个课程字典,字典里面的key就是课程名称value对应的就是课程对象
# 所以我们修改这个课程对象的score属性,即可达到修改学生这门课程分数的效果
obj.curriculum[curr].score = score
# 学生类,继承人的属性
class Student(People):
def __init__(self,name,age,sex):
super().__init__(name,age,sex)
self.curriculum = {}
# 课程属性
class Curriculum:
def __init__(self,name,cycle,price):
self.name = name
self.cycle = cycle
self.price = price
self.score = 0
tom = Teacher('tom',23,'男')
jack = Student('jack',18,'男')
# 定义多个课程
python = Curriculum('python','7个月',20000)
go = Curriculum('go','8个月',20000)
linux = Curriculum('linux','6个月',20000)
# 为学生添加课程
jack.curriculum['python'] = python
jack.curriculum['go'] = go
# 老师调用修改学生分数的方法
tom.change(jack,'python',100)
tom.change(jack,'go',98)
print('学生课程分数:')
for i in jack.curriculum:
print(f'课程名:{i} 分数:{jack.curriculum[i].score}')
相同属性的查找
对象查找属性顺序为:对象自身 > 对象的类 > 对象类的父类 > 对象类的父类的父类…
在使用继承以后,如果继承的父类还有继承,那么我们的查找顺序该是什么样的呢?现在来了解一下
class F1:
def s1(self):
print('F1.s1')
self.s2() # 关键点,它调用的是谁?
def s2(self):
print('F1.s2')
class F2(F1):
# def s1(self):
# print('F2.s1')
pass
class F3(F2):
def s1(self):
F2.s1(self)
def s2(self):
print('F3.s2')
f3 = F3()
f1.s1()
可以看到,我们F3继承了F2,然后我们需要调用F2里面的s1方法,所以进去F2查找,没有找到则继续去F2的父类里面查找,也就是F1,那么在F1里面找到以后,运行了这个s1方法,关键点就是s1方法里面self.s2调用的是谁?
我们只需要知道这个self代表谁,它代表的就是调用F3的对象,所以当我们在F1里面通过self找s2这个方法时,它会回到起始的位置,也是F3里面再重新查找,那么就找到了F3里面的s2方法
执行结果
'F1.s1'
'F3.s2'
多层关系的继承查找,永远是从最下面开始找。通过查找属性时只会把自己的对象传递进去,当上面再通过self查找属性,还是会回到起点开始查找
新式类与经典类
新式类
如果一个类继承了object类
,这个类就是新式类,在Python3中,所有类默认继承object类
。
我们可以通过一个方法来查看类的继承,类名.__bases__
可以看到这个类是继承至哪里
# 既然说到默认继承了object类,那么我们明明没有(object)继承!
class People:
pass
print(People.__bases__)
class People(object): # Python3中默认会帮助我们加上
pass
print(People.__bases__)
执行效果
# 可以看到默认继承至object
(<class 'object'>,)
(<class 'object'>,)
经典类
没有继承object的类以及该类的子类,都是经典类。只有Python2中存在
如果需要变成新式类,需要手动继承object
我们可以将解释器改为Python2中查看
我们进行手动继承object
类,再查看Python2中默认是否有继承类
class People:
pass
print People.__bases__
class People(object):
pass
print People.__bases__
执行结果:可以看到,如果不手动继承那么继承则为空
()
(<type 'object'>,)
多继承
指的是一个类可以有多个父类
class A:
def f1(self):
print('A.f1')
class B:
def f1(self):
print('B.f2')
class C(A,B):
def f3(self):
self.f1()
c = C()
c.f3()
print(C.__bases__)
执行结果
A.f1
(<class '__main__.A'>, <class '__main__.B'>) # 只能看到当前类直接继承的父类
首先当前类里面找,没有找到之后为什么是先去A里面找f1方法?这就涉及到广度优先查找
我们可以使用类名.__mro__
来查看,调用这个类找到方法是怎样的过程
print(C.__mro__)
执行结果
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
可以看到先去C里面查找,再去A里面查找,那么问题来了,如果A里面没找到的话,为什么不去它的父类object
里面查找,这就得说一下Python3新式类的查找方式。
新式类的查找方式是以广度优先查找的,怎么理解呢,介绍一下Python3中多继承的查找
菱形问题
由于Python3多继承一定会产生菱形问题(忽略object类),而Python2中不一定有菱形问题,因为经典类默认没有继承任何类。但是如果有共同父类也是会出现菱形问题。
广度优先
新式类查找属性或方法的方式,广度优先:不找多个类最后继承的同一个类,直接去找下一个父类。
如果多个类(兄弟类)继承了同一个父类,先不急着去找那个父类,而是留到最后一个继承这个父类的子类去查找,如上图所示,上面E、F都继承了G这个类,同时还有一个D继承了G这个类,记住一个点:只要继承了同一个父类,都留给最后一个继承这个父类的子类去查找,这就是广度查找
C3算法:MRO
MRO即Method Resolution Order(方法解析顺序),即在调用方法时,会对当前类以及所有的基类进行一个搜索,以确定该方法之所在,而这个搜索的顺序就是MRO。在Python2.3之前,MRO的实现是基于DFS的,而在Python2.3而在Python2.3以后MRO的实现是基于C3算法。(不具体说算法的实现,了解即可)多继承方法解析顺序
Python3中MRO顺序
使用方法:
class A:
def f1(self):
print('A.f1')
class B:
def f1(self):
print('B.f2')
class C(A,B):
def f3(self):
self.f1()
print(C.mro()) # 或者 print(C.__mro__) 推荐使用前面的
执行结果:
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
Python2.3以后的深度查找,也是遵循MRO
import inspect
class A():
def f1(self):
print('A.f1')
class B(A):
def f1(self):
print('B.f2')
class C(A):
def f3(self):
self.f1()
class D(B,C):
pass
print inspect.getmro(D)
执行结果:
(<class __main__.D at 0x7fcffb85dec0>, <class __main__.B at 0x7fcffb83f8a0>, <class __main__.A at 0x7fcffb83f7c0>, <class __main__.C at 0x7fcffb85dc20>)
可以看到,多个类同时继承了A,第一次遇到A就是去里面查找内容了,后续不会再去A里面查找
深度优先
既然提到了广度优先,那么有必要说一下经典类中的查找方式:深度优先
深度优先存在于经典类中
只要我们需要找一个属性,当前类没有就去父类找,只要有父类就会一直找下去,继承相同的父类只会找一次,和广度优先不同的是,如果在第一次就找到有相同继承的父类,那么它就会去这个父类查找是否有需要的属性或方法,后面不会再去找这个父类
小结
如果遇到多继承产生的菱形问题,那么我们只要使用MRO
就可以看到方法的查找顺序。不必太过于纠结深度与广度,大概了解即可。
super函数
super()
函数是用来调用父类的一个方法
Python3与Python2使用super()
的区别
语法:
# Python3的写法
super(). # 父类的方法或属性
# Python2的写法
super(当前类名,self). # 父类的方法或属性
使用方式:
单继承,使用super()
与使用类名.方法
效果一致。
如果是多继承的话,使用super()
调用父类是最好的选择,因为它可以解决:MRO查找顺序
,及重复调用(钻石继承)等问题
实例:
class A:
pass
# def f1(self):
# print('A.f1')
class B:
def f1(self):
print('B.f2')
class C(A,B):
def f1(self): # 使用super不会在当前类里面找,只会去父类里面查找
print('C.f1')
def f3(self):
# 如果这里我们使用类名A.f1()查找,因为是指明要到A里面去找
# 如果A里面有父类存在这个方法还好说,没有的话则报错
# 而我们使用super()可以很完美解决这个问题,先调用在A这个类里面找
# 如果A及其父类里面没有f1方法,那么则去B及其父类里面查找,再没有则报错
super().f1()
c = C()
c.f3()
super()
是遵循mro
来找父类里面的方法的
print(C.mro())
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
了解过广度优先以后可以得知,A和B同时继承了object
,那继承同一个类以后,当C找到A以后,A里面没有找到我们需要的方法,它不会去object
里面找,因为B和它继承的是一个父类,所以它会把object留给B去查找,前提是:B是最后一个继承object类的子类
清楚这个以后,再上去看一下广度优先的查找顺序图,更容易明白,只是图里面多个类继承的父类是G而已,本质上没有什么不同
super()函数的实例:
class Father(object):
def __init__(self):
self.descr = 'Father Added Attributes' # 这是我们传递进来的对象,给它增加一个属性
print('Father')
def bar(self, message):
print("%s from Father" % message)
class Child(Father):
def __init__(self):
super().__init__() # 调用父类初始化方法给对象增加一个属性
print('This is Child')
def bar(self, message):
super().bar(message) # 调用父类的bar方法,并且传递一个内容进去
print('Child bar fuction')
print(self.descr) # 我们的self.descr是调用父类初始化方法增加的属性
if __name__ == '__main__':
child = Child()
child.bar('HelloWorld') # 调用实例化对象的类里面的bar方法,因为对象本身没有,只能去这个类里面找
执行结果
'''
Father
This is Child
HelloWorld from Father
Child bar fuction
Father Added Attributes
'''
派生
派生就是子类在继承父类的基础上衍生出新的属性。子类中独有的,父类中没有的;或子类定义与父类重名属性或方法。子类也叫派生类
通俗来讲就是:派生类(子类)中定义的属性与方法称之为:派生
class A:
def f1(self):
print('A.f1')
class B(A):
def f2(self): # 派生
print('B.f2')
# 定了与父类里有相同名称的方法,在Python中也可以称为派生、其它编程语言就不一定了
def f1(self):
print('B.f1')
下一章 进阶篇(2):https://blog.csdn.net/m0_46958731/article/details/111655805
技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助请
点赞 收藏+关注
子夜欢迎您的关注,谢谢支持!