带你了解Python面向对象(2) 进阶篇:继承、多继承、新式类与经典类、MRO、派生


前言:

在我们了解面向对象基础以后,需要进一步拓展这方面的内容,接着开始了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


技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助请点赞 收藏+关注 子夜欢迎您的关注,谢谢支持!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值