浅谈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练习程序批量修改文件名
Jan 16 Python
python实现ip查询示例
Mar 26 Python
Python警察与小偷的实现之一客户端与服务端通信实例
Oct 09 Python
python冒泡排序简单实现方法
Jul 09 Python
Python实现读取邮箱中的邮件功能示例【含文本及附件】
Aug 05 Python
基于python中的TCP及UDP(详解)
Nov 06 Python
Python中协程用法代码详解
Feb 10 Python
python如何对实例属性进行类型检查
Mar 20 Python
详解Python解决抓取内容乱码问题(decode和encode解码)
Mar 29 Python
python 用户交互输入input的4种用法详解
Sep 24 Python
Python爬虫HTPP请求方法有哪些
Jun 03 Python
Python中threading库实现线程锁与释放锁
May 17 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中iconv函数使用方法
2008/05/24 PHP
php各种编码集详解和以及在什么情况下进行使用
2011/09/11 PHP
php实现singleton()单例模式实例
2014/11/06 PHP
PHP判断一个字符串是否是回文字符串的方法
2015/03/23 PHP
PHP中Enum(枚举)用法实例详解
2015/12/07 PHP
PHP实现限制IP访问及提交次数的方法详解
2017/07/17 PHP
权威JavaScript 中的内存泄露模式
2007/08/13 Javascript
js 表格隔行颜色
2009/12/02 Javascript
Jquery post传递数组方法实现思路及代码
2013/04/28 Javascript
在NodeJS中启用ECMAScript 6小结(windos以及Linux)
2014/07/15 NodeJs
原生javascript实现的分页插件pagenav
2014/08/28 Javascript
javascript正则表达式之search()用法实例
2015/01/19 Javascript
jquery+ajax实现跨域请求的方法
2015/01/20 Javascript
JS实现漂亮的窗口拖拽效果(可改变大小、最大化、最小化、关闭)
2015/10/10 Javascript
总结Javascript中数组各种去重的方法
2016/10/04 Javascript
jQuery progressbar通过Ajax请求实现后台进度实时功能
2016/10/11 Javascript
Bootstrap中data-target 到底是什么
2017/02/14 Javascript
Vue.js仿微信聊天窗口展示组件功能
2017/08/11 Javascript
AngularJS实现图片上传和预览功能的方法分析
2017/11/08 Javascript
解决vue项目报错webpackJsonp is not defined问题
2018/03/14 Javascript
Mint UI组件库CheckList使用及踩坑总结
2018/12/20 Javascript
详解Vue 换肤方案验证
2019/08/28 Javascript
基于form-data请求格式详解
2019/10/29 Javascript
vue中watch和computed为什么能监听到数据的改变以及不同之处
2019/12/27 Javascript
[01:01:22]VGJ.S vs OG 2018国际邀请赛淘汰赛BO3 第一场 8.22
2018/08/23 DOTA
python自带的http模块详解
2016/11/06 Python
keras 自定义loss损失函数,sample在loss上的加权和metric详解
2020/05/23 Python
keras实现VGG16方式(预测一张图片)
2020/07/07 Python
高中数学教学反思
2014/01/30 职场文书
辩论赛主持词
2014/03/18 职场文书
横店影视城导游词
2015/02/06 职场文书
2015年银行柜员工作总结报告
2015/04/01 职场文书
篮球赛闭幕式主持词
2015/07/03 职场文书
2016医师资格考试考生诚信考试承诺书
2016/03/25 职场文书
OpenCV图像变换之傅里叶变换的一些应用
2021/07/26 Python
HTML5中的DOCUMENT.VISIBILITYSTATE属性详解
2023/05/07 HTML / CSS