浅谈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使用xlrd读取Excel格式文件的方法
Mar 10 Python
Python中条件判断语句的简单使用方法
Aug 21 Python
Python实现Logger打印功能的方法详解
Sep 01 Python
python去除扩展名的实例讲解
Apr 23 Python
Django框架的使用教程路由请求响应的方法
Jul 03 Python
python开发之anaconda以及win7下安装gensim的方法
Jul 05 Python
django 通过url实现简单的权限控制的例子
Aug 16 Python
用python3读取python2的pickle数据方式
Dec 25 Python
解决Python命令行下退格,删除,方向键乱码(亲测有效)
Jan 16 Python
Pytorch 计算误判率,计算准确率,计算召回率的例子
Jan 18 Python
python中strip(),lstrip(),rstrip()函数的使用讲解
Nov 17 Python
基于Python的图像阈值化分割(迭代法)
Nov 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-accelerator网站加速PHP缓冲的方法
2008/07/30 PHP
解决file_get_contents无法请求https连接的方法
2013/12/17 PHP
ThinkPHP中关联查询实例
2014/12/02 PHP
php判断str字符串是否是xml格式数据的方法示例
2017/07/26 PHP
jquery 入门教程 [翻译] 推荐
2009/08/17 Javascript
淘宝搜索框效果实现分析
2011/03/05 Javascript
Ajax执行顺序流程及回调问题分析
2012/12/10 Javascript
Js 回车换行处理的办法及replace方法应用
2013/01/24 Javascript
setTimeout()与setInterval()方法区别介绍
2013/12/24 Javascript
用jquery写的菜单从左往右滑动出现
2014/04/11 Javascript
举例讲解JavaScript中关于对象操作的相关知识
2015/11/16 Javascript
详解Bootstrap glyphicons字体图标
2016/01/04 Javascript
Bootstrap开发实战之第一次接触Bootstrap
2016/06/02 Javascript
jQuery获取select选中的option的value值实现方法
2016/08/29 Javascript
form表单序列化详解(推荐)
2017/08/15 Javascript
jQuery实现轮播图及其原理详解
2020/04/12 jQuery
详解vue-cli 2.0配置文件(小结)
2019/01/14 Javascript
VUE v-model表单数据双向绑定完整示例
2019/01/21 Javascript
js之切换全屏和退出全屏实现代码实例
2019/09/09 Javascript
利用Python进行异常值分析实例代码
2017/12/07 Python
DataFrame中去除指定列为空的行方法
2018/04/08 Python
Python快速查找list中相同部分的方法
2018/06/27 Python
对python生成业务报表的实例详解
2019/02/03 Python
python调用并链接MATLAB脚本详解
2019/07/05 Python
python将unicode和str互相转化的实现
2020/05/11 Python
CSS3 text-shadow实现文字阴影效果
2016/02/24 HTML / CSS
HTML5本地存储localStorage、sessionStorage基本用法、遍历操作、异常处理等
2014/05/08 HTML / CSS
斯凯奇澳大利亚官网:SKECHERS澳大利亚
2018/03/31 全球购物
管理站站长岗位职责
2013/11/27 职场文书
优秀辅导员事迹材料
2014/02/16 职场文书
公务员个人考察材料
2014/12/23 职场文书
2016年三严三实党课学习心得体会
2016/01/06 职场文书
超级详细实用的pycharm常用快捷键
2021/05/12 Python
详解Redis复制原理
2021/06/04 Redis
MySQL 发生同步延迟时Seconds_Behind_Master还为0的原因
2021/06/21 MySQL
JavaScript的function函数详细介绍
2021/11/20 Javascript