欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python多继承及MRO顺序

程序员文章站 2022-05-21 11:25:13
...

多继承的实现

class A(object):

    def out(self):
        print("A类方法")

class B(object):

    def out(self):
        print("B类方法")

class C(A, B):
    pass

c = C()
# 打印C类的调用路径顺序(注意要类名.__mro__)
print(C.__mro__)
c.out()

运行结果:

(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
A类方法

可以尝试一下把 C类 的继承顺序改成 B,A

class C(B, A):
    pass

结果就是

(<class ‘main.C’>, <class ‘main.B’>, <classobject>, <class ‘main.A’>)
B类方法

如果 C类out() 方法重写那么将执行 C类的 out() 方法

class C(B, A):
    
    def out(self):
        print("C类方法")

结果如下:

(<class ‘main.C’>, <class ‘main.B’>, <classobject>, <class ‘main.A’>)
C类方法

了解MRO

新式类可以直接通过 类名.__mro__ 的方式获取类的 MRO,也可以通过 类名.mro() 的形式,旧式类是没有 mro 属性和 mro() 方法的。

方法解析顺序 Method Resolution Order,简称 MRO。主要用于在多继承时判断方法,属性的调用路径。

  • 在搜索方法时,是按照 mro() 输出的结果,从左到右的顺序查找的
  • 如果找到,在当前类中找到方法就直接执行,不在搜索
  • 没有找到,就依次查找下一个类中是否有对应的方法,找到执行,不在搜索
  • 如果最后一个类,还没有找到方法,程序报错

MRO 的顺序是根据 Python中C3算法 得来的大家感兴趣可以去研究一下,这里就不在赘述了。


新式类和旧式类

在早期版本的 Python 中,所有类并没有一个共同的祖先 object,如果定义一个类,但没有显式指定其祖先,那么就被解释为 旧式类,例如:

class oldA:  
	pass

class oldB:
    pass

其中,oldAoldB 都属于旧式类

Python 2.x 版本中,为了向后兼容保留了旧式类。该版本中的 新式类必须 显式继承 object 或者其他新式类:

class NewA(object):  
    pass

class NewB(NewA):  
    pass

显然,以上两个类都属于 新式类

而在 Python 3.x 版本中,不再保留旧式类的概念。因此,没有继承任何其他类的类都隐式地继承自 object


super()的使用

super() 函数是用于调用父类(超类)的一个方法。

super() 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。


单继承

父类名调用

"""
单继承使用父类名调用
"""

class Parent(object):

	def eat(self):
		print("\tparent --- 爱吃饭")


class Son1(Parent):


	def eat(self):
		print("son1 --- eat()")
		Parent.eat(self)
		print("\tson1  ---  爱吃蔬菜\n")
	

class Son2(Parent):


	def eat(self):
		print("son2 --- eat()")
		Parent.eat(self)
		print("\tson2  ---  爱吃水果\n")


def main():
	s1 = Son1()
	s2 = Son2()

	s1.eat()
	s2.eat()

    
if __name__ == '__main__':
	main()

运行结果

son1 --- eat()
	parent --- 爱吃饭
	son1  ---  爱吃蔬菜

son2 --- eat()
	parent --- 爱吃饭
	son2  ---  爱吃水果

使用super()

"""
单继承中super()的使用
"""

class Parent(object):

	def eat(self):
		print("\tparent --- 爱吃饭")


class Son1(Parent):


	def eat(self):
		print("son1 --- eat()")
		super().eat()
		print("\tson1  ---  爱吃蔬菜\n")
	

class Son2(Parent):


	def eat(self):
		print("son2 --- eat()")
		super().eat()
		print("\tson2  ---  爱吃水果\n")


def main():
	s1 = Son1()
	s2 = Son2()

	s1.eat()
	s2.eat()


if __name__ == '__main__':
	main()

运行结果:

son1 --- eat()
	parent --- 爱吃饭
	son1  ---  爱吃蔬菜

son2 --- eat()
	parent --- 爱吃饭
	son2  ---  爱吃水果

可以发现在单继承使用 父类名super() 调用父类方法结果都一样。


多继承

还是上面例子,就是加一个 GrandSon 类,让它继承 Son1, Son2 。让 eat() 具备其父类的特性。

父类名调用

"""
多继承中父类名的使用
"""

class Parent(object):

	def eat(self):
		print("\tparent --- 爱吃饭")


class Son1(Parent):


	def eat(self):
		print("son1 --- eat()")
		Parent.eat(self)
		print("\tson1  ---  爱吃蔬菜\n")
	

class Son2(Parent):


	def eat(self):
		print("son2 --- eat()")
		Parent.eat(self)
		print("\tson2  ---  爱吃水果\n")


class Grandson(Son1, Son2):
	
	def eat(self):
		print("grandson --- eat()")
		# super().eat()
		Son1.eat(self)
		Son2.eat(self)
		print("\tgrandson --- 爱吃零食")


def main():
	# s1 = Son1()
	# s2 = Son2()

	# s1.eat()
	# s2.eat()

	g = Grandson()
	g.eat()


if __name__ == '__main__':
	main()

运行结果

grandson --- eat()
son1 --- eat()
	parent --- 爱吃饭
	son1  ---  爱吃蔬菜

son2 --- eat()
	parent --- 爱吃饭
	son2  ---  爱吃水果

	grandson --- 爱吃零食

结果显示 Parenteat() 方法调用了多次,存在重复调用。


使用super()

"""
多继承中super()的使用
"""

class Parent(object):

	def eat(self):
		print("\tparent --- 爱吃饭")


class Son1(Parent):


	def eat(self):
		print("son1 --- eat()")
		super().eat()
		print("\tson1  ---  爱吃蔬菜\n")
	

class Son2(Parent):


	def eat(self):
		print("son2 --- eat()")
		super().eat()
		print("\tson2  ---  爱吃水果\n")


class Grandson(Son1, Son2):
	
	def eat(self):
		print("grandson --- eat()")
		super().eat()
		print("\tgrandson --- 爱吃零食")


def main():
	g = Grandson()
	g.eat()


if __name__ == '__main__':
	main()

运行结果

grandson --- eat()
son1 --- eat()
son2 --- eat()
	parent --- 爱吃饭
	son2  ---  爱吃水果

	son1  ---  爱吃蔬菜

	grandson --- 爱吃零食

可以发现在多继承中使用 super() 没有重复调用。


假如在多继承中 Grandson 类的 eat() 方法只想复用 Parent, Son1eat()的方法,不需要 Son2的。该如何实现呢?

父类名调用

"""
父类名的使用
"""

class Parent(object):

	def eat(self):
		print("\tparent --- 爱吃饭")


class Son1(Parent):

	def eat(self):
		print("son1 --- eat()")
		Parent.eat(self)
		print("\tson1  ---  爱吃蔬菜\n")
	

class Son2(Parent):

	def eat(self):
		print("son2 --- eat()")
		Parent.eat(self)
		print("\tson2  ---  爱吃水果\n")


class Grandson(Son1, Son2):
	
	def eat(self):
		print("grandson --- eat()")
		Son1.eat(self)
		# Son2.eat(self)
		print("\tgrandson --- 爱吃零食")


def main():
	print(Grandson.mro())
	g = Grandson()
	g.eat()


if __name__ == '__main__':
	main()

结果如下

[<class '__main__.Grandson'>, 
 <class '__main__.Son1'>, 
 <class '__main__.Son2'>, 
 <class '__main__.Parent'>, 
 <class 'object'>
]

grandson --- eat()
son1 --- eat()
	parent --- 爱吃饭
	son1  ---  爱吃蔬菜

	grandson --- 爱吃零食
[Finished in 0.1s]

super()调用

"""
super()的使用
"""

class Parent(object):

	def eat(self):
		print("\tparent --- 爱吃饭")


class Son1(Parent):

	def eat(self):
		print("son1 --- eat()")
		super().eat()
		print("\tson1  ---  爱吃蔬菜\n")
	

class Son2(Parent):

	def eat(self):
		print("son2 --- eat()")
		super().eat()
		print("\tson2  ---  爱吃水果\n")


class Grandson(Son1, Son2):
	
	def eat(self):
		print("grandson --- eat()")
		super(Son1, self).eat()
		# Son1.eat(self)
		# Son2.eat(self)
		print("\tgrandson --- 爱吃零食")


def main():
	print(Grandson.mro())
	g = Grandson()
	g.eat()


if __name__ == '__main__':
	main()

结果如下

[<class '__main__.Grandson'>, 
 <class '__main__.Son1'>, 
 <class '__main__.Son2'>, 
 <class '__main__.Parent'>, 
 <class 'object'>
]

grandson --- eat()
son2 --- eat()
	parent --- 爱吃饭
	son2  ---  爱吃水果

	grandson --- 爱吃零食
[Finished in 0.1s]

然而却发现 super(Son1, self).eat() 调用的是 Son2eat() 方法。

是因为 MRO 的原因,当调用 super(Son1,self).eat() 时 ,会拿 Son1GrandsonMRO方法解析顺序表 中寻找,找到然后 super() 调用则是列表中下一个,这里是 Son2,然后 Son2.eat() 中使用了 super().eat(),此时是拿其本身 Son2Son2MRO方法解析顺序表 中寻找,然后 super().eat() 调用,则是列表的下一个 Parent.eat()

因此在 Grandsonsuper(Son1, self).eat() 调用的是 Son2.eat()

假如 super(Son2, self).eat() 调用的则是 Parent.eat()。结果如下

[<class '__main__.Grandson'>, 
 <class '__main__.Son1'>, 
 <class '__main__.Son2'>, 
 <class '__main__.Parent'>, 
 <class 'object'>
]
grandson --- eat()
	parent --- 爱吃饭
	grandson --- 爱吃零食

因此只能使用 父类名 的形式调用 Son1.eat()只继承 Son1 的特性。如果不清楚 super()调用的谁,打印其

类名.mro(),对照 MRO方法解析顺序表,就一目了然。


总结

  • 方法解析顺序 Method Resolution Order,简称 MRO。主要用于在多继承时判断方法,属性的调用路径

  • 旧式类,没有共同的 object祖先且没有显式的指定其祖先。

  • 新式类,在 Python 2.x 版本中显式继承 object 或者其他新式类,Python3.x中则是隐式继承object

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

  • 多继承 上有区别,super() 方法 能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次,具体看前面的输出结果


公众号

新建文件夹X

大自然用数百亿年创造出我们现实世界,而程序员用几百年创造出一个完全不同的虚拟世界。我们用键盘敲出一砖一瓦,用大脑构建一切。人们把1000视为权威,我们反其道行之,捍卫1024的地位。我们不是键盘侠,我们只是平凡世界中不凡的缔造者 。