浅谈Python的方法解析顺序(MRO)


Posted in Python onMarch 05, 2020

方法解析顺序, Method Resolution Order

从一段代码开始

考虑下面的情况:

class A(object):
 def foo(self):
 print('A.foo()')

class B(object):
 def foo(self):
 print('B.foo()')

class C(B, A):
 pass

c = C()
c.foo()

C同时继承了类A和类B, 它们都有各自的foo()方法. 那么C的实例c调用foo()方法时, 到底是调用A.foo()还是B.foo()?

__mro__

Python的每一个有父类的类都有一个与方法解析顺序相关的特殊属性:__mro__, 它是一个tuple, 装着方法解析时的对象查找顺序: 越靠前的优先级越高. 执行下面的代码:

print type(C.__mro__)
print C.__mro__

输出:

<type 'tuple'>
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <type 'object'>)

可以看到, B在C的前面, 所以在上一段代码中, c.foo()调用的是B.foo()而不是A.foo().

之所以B在C的前面, 是因为在指定C的父类时先指定了B:

class C(B, A):

若将它改成:

class C(A, B):

c.foo()执行的就是A.foo()了.

熟悉环境变量的可以将__mro__理解为以目标对象为环境的PATH变量: 从左到右开始查找, 找到就执行, 然后返回结果.

方法解析顺序

从C.__mro__的值可以看出, Python的方法解析优先级从高到低为:

1. 实例本身(instance)

2. 类(class)

3. super class, 继承关系越近, 越先定义, 优先级越高.

其实属性解析顺序也基本一致, 只不过多了个__getattr__的查找(见Python对象的属性访问过程).

补充知识:python中的单继承,多继承和mro顺序

python作为一门动态语言,是和c++一样支持面向对象编程的。相对对象编程有三大特性,分别是继承,封装和多态。今天我们重点讲解的是,python语言中的单继承和多继承。

继承概念:

如果一个类继承了另外一个类时,它将自动获得另一个类的所有属性和方法,那么原有的类称为父类,而新类称为子类。子类继承了其父类的所有属性和方法。同时还可以定义自己的属性和方法。
单继承就是一个子类只能继承一个父类。

格式: class 子类(父类)

举例: class A(B)

A类拥有了B类的所有的特征,A类继承了B类

B类 父类,基类

A类 子类 派生类 后代类

继承的作用:功能的升级和扩展
功能的升级就是对原有 的功能进行完善重新,功能的扩展就是对原本没有的功能进行添加。减少代码的冗余。

下面我们举一个单继承的例子:

class Dog(): #父类
 def __init__(self): #父类的属性初始化
 self.name='狗'
 self.leg=4
 def __str__(self):
 return "名字:%s %d 条腿"%(self.name,self.leg)

class Taidi(Dog): #定义一个Taidi 泰迪 类继承自Dog类 -->单继承
 pass

taidi=Taidi()
print(taidi) 输出结果--> 名字:狗 4 条腿

多继承:

多继承就是一个子类同时继承自多个父类,又称菱形继承、钻石继承。

首先,我们先讲多继承中一个常见方法,单独调用父类的方法。在子类初始化的时候需要手动调用父类的初始化方法进行父类的属性的构造,不然就不能使用提供的属性。

在子类中调用父类的初始化方法格式就是: 父类名._init_(self)

下面举一个单独调用父类方法的例子:

print("******多继承使用类名.__init__ 发生的状态******")
class Parent(object): #父类
 def __init__(self, name):
 print('parent的init开始被调用')
 self.name = name #属性的初始化
 print('parent的init结束被调用')

class Son1(Parent): #单继承 Son1子类继承父类
 def __init__(self, name, age):
 print('Son1的init开始被调用')
 self.age = age
 Parent.__init__(self, name) #单独调用父类的属性
 print('Son1的init结束被调用')

class Son2(Parent): #也是单继承 Son2继承父类
 def __init__(self, name, gender):
 print('Son2的init开始被调用')
 self.gender = gender #单独调用父类的初始化属性方法
 Parent.__init__(self, name)
 print('Son2的init结束被调用')

class Grandson(Son1, Son2): #多继承,继承两个父类
 def __init__(self, name, age, gender):
 print('Grandson的init开始被调用')
 Son1.__init__(self, name, age) # 单独调用父类的初始化方法
 Son2.__init__(self, name, gender)
 print('Grandson的init结束被调用')

gs = Grandson('grandson', 18, '男') #实例化对象
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)

print("******多继承使用类名.__init__ 发生的状态******\n\n")

下面让我们看看运行的结果:

******多继承使用类名.__init__ 发生的状态******
Grandson的init开始被调用
Son1的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son1的init结束被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 18
性别: 男
******多继承使用类名.__init__ 发生的状态******

mro顺序

查看上面的运行结果,我们发现由于多继承情况,parent类被的属性被构造了两次,如果在更加复杂的结构下可能更加严重。

为了解决这个问题,Python官方采用了一个算法将复杂结构上所有的类全部都映射到一个线性顺序上,而根据这个顺序就能够保证所有的类都会被构造一次。这个顺序就是MRO顺序。

格式:

类名._mro_()

类名.mro()

多继承中super调用有所父类的被重写的方法

super本质上就是使用MRO这个顺序去调用 当前类在MRO顺序中下一个类。 super().init()则调用了下一个类的初始化方法进行构造。

print("******多继承使用super().__init__ 发生的状态******")
class Parent(object):
 def __init__(self, name, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
 print('parent的init开始被调用')
 self.name = name
 print('parent的init结束被调用')

class Son1(Parent):
 def __init__(self, name, age, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
 print('Son1的init开始被调用')
 self.age = age
 super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
 print('Son1的init结束被调用')

class Son2(Parent):
 def __init__(self, name, gender, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
 print('Son2的init开始被调用')
 self.gender = gender
 super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
 print('Son2的init结束被调用')

class Grandson(Son1, Son2):
 def __init__(self, name, age, gender):
 print('Grandson的init开始被调用')
 # 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
 # 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
 # super(Grandson, self).__init__(name, age, gender)
 super().__init__(name, age, gender)
 print('Grandson的init结束被调用')

print(Grandson.__mro__)

gs = Grandson('grandson', 18, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
print("******多继承使用super().__init__ 发生的状态******\n\n")

查看下运行结果:

******多继承使用super().__init__ 发生的状态******
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
Grandson的init开始被调用
Son1的init开始被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 18
性别: 男
******多继承使用super().__init__ 发生的状态******

单继承中super

print("******单继承使用super().__init__ 发生的状态******")
class Parent(object):
 def __init__(self, name):
 print('parent的init开始被调用')
 self.name = name
 print('parent的init结束被调用')

class Son1(Parent):
 def __init__(self, name, age):
 print('Son1的init开始被调用')
 self.age = age
 super().__init__(name) # 单继承不能提供全部参数
 print('Son1的init结束被调用')

class Grandson(Son1):
 def __init__(self, name, age, gender):
 print('Grandson的init开始被调用')
 super().__init__(name, age) # 单继承不能提供全部参数
 print('Grandson的init结束被调用')

gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
#print('性别:', gs.gender)
print("******单继承使用super().__init__ 发生的状态******\n\n")

运行结果:

******单继承使用super().__init__ 发生的状态******
Grandson的init开始被调用
Son1的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
******单继承使用super().__init__ 发生的状态******

下面让我们总结下:

MRO保证了多继承情况 每个类只出现一次

super().__init__相对于类名.init,在单继承上用法基本无差

但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次

多继承时,使用super方法,对父类的传参数,应该是由于python中super的算法导致的原因,必须把参数全部传递,否则会报错

单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错

多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍,而使用super方法,只需写一句话便执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因

下面是一个简答的面试题:

class Parent(object):
 x = 1

class Child1(Parent):
 pass

class Child2(Parent):
 pass

print(Parent.x, Child1.x, Child2.x)
Child1.x = 2
print(Parent.x, Child1.x, Child2.x)
Parent.x = 3
print(Parent.x, Child1.x, Child2.x)

运行结果:

1 1 1
1 2 1
3 2 3

以上这篇浅谈Python的方法解析顺序(MRO)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python中处理字符串的相关的len()方法的使用简介
May 19 Python
Python简单实现TCP包发送十六进制数据的方法
Apr 16 Python
Python实现文件复制删除
Apr 19 Python
python 读写txt文件 json文件的实现方法
Oct 22 Python
Python实现列表删除重复元素的三种常用方法分析
Nov 24 Python
详解Python中 sys.argv[]的用法简明解释
Dec 20 Python
利用Django-environ如何区分不同环境
Aug 26 Python
对Python subprocess.Popen子进程管道阻塞详解
Oct 29 Python
对Python定时任务的启动和停止方法详解
Feb 19 Python
详解python实现可视化的MD5、sha256哈希加密小工具
Sep 14 Python
python中把元组转换为namedtuple方法
Dec 09 Python
如何用 Python 制作 GitHub 消息助手
Feb 20 Python
python环境下安装opencv库的方法
Mar 05 #Python
Python序列化pickle模块使用详解
Mar 05 #Python
Window系统下Python如何安装OpenCV库
Mar 05 #Python
Python bytes string相互转换过程解析
Mar 05 #Python
Python 从attribute到property详解
Mar 05 #Python
Python+OpenCV实现图像的全景拼接
Mar 05 #Python
Python对象的属性访问过程详解
Mar 05 #Python
You might like
PHP MSSQL 存储过程的方法
2008/12/24 PHP
解决PHP在DOS命令行下却无法链接MySQL的技术笔记
2010/12/29 PHP
如何在php中正确的使用json
2013/08/06 PHP
php递归创建目录的方法
2015/02/02 PHP
PHP数组游标实现对数组的各种操作详解
2016/01/26 PHP
浅谈PHP命令执行php文件需要注意的问题
2016/12/16 PHP
Aster vs KG BO3 第二场2.19
2021/03/10 DOTA
jQuery 使用手册(一)
2009/09/23 Javascript
jquery 获取标签名(tagName)示例代码
2013/07/11 Javascript
2种jQuery 实现刮刮卡效果
2015/02/01 Javascript
使用jquery清空、复位整个输入域
2015/04/02 Javascript
javascript的BOM汇总
2015/07/16 Javascript
jquery实现实时改变网页字体大小、字体背景色和颜色的方法
2015/08/05 Javascript
Angular2学习笔记——详解路由器模型(Router)
2016/12/02 Javascript
js实现常见的工具条效果
2017/03/02 Javascript
非常实用的vue导航钩子
2017/03/20 Javascript
Node.js实现一个HTTP服务器的方法示例
2019/05/13 Javascript
原生JS使用Canvas实现拖拽式绘图功能
2019/06/05 Javascript
javascript-hashchange事件和历史状态管理实例分析
2020/04/18 Javascript
python设置windows桌面壁纸的实现代码
2013/01/28 Python
浅析Python与Mongodb数据库之间的操作方法
2019/07/01 Python
Python 实现try重新执行
2019/12/21 Python
Django User 模块之 AbstractUser 扩展详解
2020/03/11 Python
django xadmin中form_layout添加字段显示方式
2020/03/30 Python
pandas之分组groupby()的使用整理与总结
2020/06/18 Python
python实现每天自动签到领积分的示例代码
2020/08/18 Python
CSS实现进度条和订单进度条的示例
2020/11/05 HTML / CSS
HTML5 文件域+FileReader 分段读取文件并上传到服务器
2017/10/23 HTML / CSS
LightInTheBox西班牙站点:全球商品在线采购
2016/09/22 全球购物
VICHY薇姿英国官网:全球专业敏感肌护肤领先品牌
2017/07/04 全球购物
Skyscanner波兰:廉价航班
2017/11/07 全球购物
改变生活的男士内衣:SAXX Underwear
2019/08/28 全球购物
新年祝酒词大全
2015/08/11 职场文书
为什么node.js不适合大型项目
2021/04/28 Javascript
使用Oracle跟踪文件的问题详解
2021/06/28 Oracle
redis 解决库存并发问题实现数量控制
2022/04/08 Redis