浅谈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复制与引用用法分析
Apr 08 Python
实践Python的爬虫框架Scrapy来抓取豆瓣电影TOP250
Jan 20 Python
python 使用turtule绘制递归图形(螺旋、二叉树、谢尔宾斯基三角形)
May 30 Python
如何用Python制作微信好友个性签名词云图
Jun 28 Python
详细介绍pandas的DataFrame的append方法使用
Jul 31 Python
python 生成器和迭代器的原理解析
Oct 12 Python
后端开发使用pycharm的技巧(推荐)
Mar 27 Python
windows python3安装Jupyter Notebooks教程
Apr 13 Python
python 爬取腾讯视频评论的实现步骤
Feb 18 Python
python迷宫问题深度优先遍历实例
Jun 20 Python
python百行代码实现汉服圈图片爬取
Nov 23 Python
Python matplotlib绘制条形统计图 处理多个实验多组观测值
Apr 21 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代码优化及php相关问题总结
2006/10/09 PHP
php切割页面div内容的实现代码分享
2012/07/31 PHP
php图片添加文字水印实现代码
2016/03/15 PHP
分析PHP中单双引号的误区和双引号小隐患
2016/07/19 PHP
php微信公众账号开发之前五个坑(一)
2016/09/18 PHP
php+resumablejs实现的分块上传 断点续传功能示例
2017/04/18 PHP
php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia)
2020/02/15 PHP
一个不错的用JavaScript实现的UBB编码函数
2007/03/09 Javascript
javascript Promise简单学习使用方法小结
2016/05/17 Javascript
将List对象列表转换成JSON格式的类实现方法
2016/07/04 Javascript
BootStrap 轮播插件(carousel)支持左右手势滑动的方法(三种)
2016/07/07 Javascript
AngularJS实现的JSONP跨域访问数据传输功能详解
2017/07/20 Javascript
Bootstrap栅格系统的使用详解
2017/10/30 Javascript
Angular 项目实现国际化的方法
2018/01/08 Javascript
vue两个组件间值的传递或修改方式
2018/07/04 Javascript
ES6 Promise对象的应用实例分析
2019/06/27 Javascript
pycharm 使用心得(六)进行简单的数据库管理
2014/06/06 Python
Python中尝试多线程编程的一个简明例子
2015/04/07 Python
Python 稀疏矩阵-sparse 存储和转换
2017/05/27 Python
Python3.5内置模块之random模块用法实例分析
2019/04/26 Python
python 哈希表实现简单python字典代码实例
2019/09/27 Python
python 中的paramiko模块简介及安装过程
2020/02/29 Python
Python异常原理及异常捕捉实现过程解析
2020/03/25 Python
Python中操作各种多媒体,视频、音频到图片的代码详解
2020/06/04 Python
python中二分查找法的实现方法
2020/12/06 Python
css3 按钮 利用css3实现超酷下载按钮
2013/03/18 HTML / CSS
浅谈html5标签css3的常用样式
2016/10/20 HTML / CSS
加拿大鞋子连锁店:Town Shoes
2016/09/26 全球购物
社团2014年植树节活动总结
2014/03/11 职场文书
企业党员公开承诺书
2014/03/26 职场文书
产品陈列协议书(标准版)
2014/09/17 职场文书
道路交通事故赔偿协议书
2014/10/24 职场文书
2015年毕业生自荐信范文
2015/03/24 职场文书
2016国庆节67周年红领巾广播稿
2015/12/18 职场文书
python tkinter Entry控件的焦点移动操作
2021/05/22 Python
详解apache编译安装httpd-2.4.54及三种风格的init程序特点和区别
2022/07/15 Servers