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

Python - 多继承中的super()与MRO

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

Python是完全的面向对象的语言,因此它具有面向对象语言的特点: 封装,继承,多态。

在学习PyTorch定义模块时遇到了这个问题,趁此机会把Python多继承的细节搞清楚。

继承:

单继承:

#父类
class Animal(object):
	"""docstring for Animal"""
	def __init__(self, name):
		self.name = name;

	def run(self):
		print("[{0}] is running".format(self.name))

	def eat(self):
		print("[{0}] is eatting".format(self.name))
		
#子类
class Dog(Animal):
	def __init__(self, name):
		self.name = name;
	# 子类调用父类中已有的方法
	def animal_run(self):
		print("\nCalling father's method:")
		Animal.run(self)


dog = Dog("Chichi")
dog.run()
dog.eat()
dog.animal_run()

运行结果:

[Chichi] is running
[Chichi] is eatting

Calling father's method:
[Chichi] is running

在单继承的情况下,我们可以直接在子类中通过 父类名.Method() 来调用父类的方法。

多继承:

class A(object):
    def __init__(self):
        self.n = 'A'
        print("I'm A, I am called from C")
    def func(self):
        print("self is {0} in A.func".format(self))
        self.n += 'A'


class B(A):
    def __init__(self):
        self.n = 'B'
        print("I'm B, I am called from D")
    def func(self):
        print("self is {0} in B.func".format(self))
        super().func()
        self.n += 'B'


class C(A):
    def __init__(self):
        self.n = 'C'
        print("I'm C, I am called from B")

    def func(self):
        print("self is {0} in C.func".format(self))
        super().func()
        self.n += 'C'


class D(B, C):
    def __init__(self):
        self.n = 'D'
        super(D, self).func()

    def func(self):
        print("self is {0} in D.func".format(self))
        # super().func()
        print(D.mro())
        A.__init__(self)
        print("Calling by super():")
        super(B,self).__init__()
        self.n += 'D'

d = D()
d.func()

运行结果:

self is <__main__.D object at 0x10ff3d7b8> in B.func
self is <__main__.D object at 0x10ff3d7b8> in C.func
self is <__main__.D object at 0x10ff3d7b8> in A.func
self is <__main__.D object at 0x10ff3d7b8> in D.func
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
I'm A, I am called from C
Calling by super():
I'm C, I am called from B

在多继承中,我们也可以直接指明父类名并调用其中的方法,但这样做需要我们直接写出父类名,这在很多时候是很麻烦的事情。当该子类的父类名称变化时,我们就需要修改很多地方的调用,所以在Python2中引入了super()方法。

1. 我们在写D的__init__()方法时,可以直接用如下代码来调用其父类的初始化方法,然后在父类的基础上继续添加子类的个性化方法。

super(D,self).__init__()

2. 甚至我们可以完全不写D的__init__(),当创建D的对象时,自动从父类继承__init()__方法。

但在多继承中,

1. 一个子类有两个父类时,应该调用哪个父类的方法呢?

2. 该例中,super是怎么决定调用B还是C的init的呢?

3. 或者当直接的父类也没有init函数,那是不是就直接继续去找父类的父类呢?

Python多继承问题实质上是钻石继承问题(Diamond Inheritance):

Python - 多继承中的super()与MRO

对于该问题的解决方法是,规定一个MRO(Method Resolution Order),所以在上例中,MRO是D->B->C->A->Object, 按该顺序调用我们就能找到上面问题的答案。

1. 多继承中,子类会按照MRO调用排在自己下一个的类中的方法。

2. 按照MRO中的顺序调用。

3. 当最初被调用的类没有该方法时,Python会按照MRO的顺序一个一个向前查找,直到找到该方法。但需要注意的是,当D,B都没有定义init时,调用的是C 而不是A 的init。

那么MRO顺序是怎么确定的呢?

一句话总结:父类总是出现在子类后,若有多个父类,其相对顺序保持不变,在定义子类调参时,父类的顺序就已经决定了MRO顺序。

这里有一篇对MRO顺序的探究,给出了很多有趣的例子。