Python方法解析顺序(MRO)详解:从__mro__到super多继承实战
本文深入解析Python的方法解析顺序(MRO),通过__mro__属性揭示类继承的查找优先级。对比单继承与多继承中super()与类名.__init__的行为差异,重点讲解钻石继承结构下的初始化调用链,确保每个父类方法仅执行一次,并提供多继承传参的最佳实践。
Python方法解析顺序(MRO)详解:从__mro__到super多继承实战
方法解析顺序(Method Resolution Order,简称MRO)
MRO基础:__mro__属性与查找优先级
让我们通过一段具体的代码示例来引入这个话题。
假设存在如下场景:
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并调用c.foo(),Python解释器究竟会执行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类之后、A类之前。因此在上述代码中,c.foo()实际调用的是B.foo()而非A.foo()。
造成B类排在C类前面的原因在于定义C类时父类的声明顺序:
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顺序与super调用机制
MRO顺序
观察上述运行结果可以发现,由于采用了多继承结构,Parent类的属性被构造了两次。如果在更复杂的继承结构中,这种情况可能会更加严重。
为了解决这一问题,Python官方采用了一种算法,将复杂结构中的所有类映射到一个线性顺序上。依据这个顺序,可以保证每个类的方法仅被构造一次。这个顺序即为MRO顺序。
查看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__ 发生的状态******
总结:super与类名调用的多继承差异
下面让我们对上述内容进行总结:
MRO机制保证了在多继承情况下,每个类只会出现一次。
在单继承场景中,super().__init__与类名.__init__的用法基本没有差异。
但在多继承场景下存在显著区别: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)的浅显探讨就是全部内容,希望能为大家提供参考,也希望大家多多支持本站。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













