super()如何处理多重继承?例如,给定:

class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print "that's it"

Third的哪个父方法执行super()。__init__ refer to?我可以选择哪些运行吗?

我知道这与方法解析顺序(MRO)有关。


当前回答

我想用“无生命”来详细说明这个答案,因为当我开始阅读如何在Python的多重继承层次结构中使用super()时,我并没有立即得到它。

你需要了解的是super(MyClass, self).__init__()在完整继承层次结构的上下文中根据所使用的方法解析排序(MRO)算法提供下一个__init__方法。

理解这最后一部分至关重要。让我们再考虑一下这个例子:

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

根据Guido van Rossum关于方法解析顺序的文章,解析__init__的顺序是使用“深度优先的从左到右遍历”来计算的(在Python 2.3之前):

Third --> First --> object --> Second --> object

删除所有重复项后,除了最后一个,我们得到:

Third --> First --> Second --> object

那么,让我们来看看当我们实例化一个Third类的实例时会发生什么,例如x = Third()。

According to MRO Third.__init__ executes. prints Third(): entering then super(Third, self).__init__() executes and MRO returns First.__init__ which is called. First.__init__ executes. prints First(): entering then super(First, self).__init__() executes and MRO returns Second.__init__ which is called. Second.__init__ executes. prints Second(): entering then super(Second, self).__init__() executes and MRO returns object.__init__ which is called. object.__init__ executes (no print statements in the code there) execution goes back to Second.__init__ which then prints Second(): exiting execution goes back to First.__init__ which then prints First(): exiting execution goes back to Third.__init__ which then prints Third(): exiting

这详细说明了为什么实例化Third()会导致:

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

从Python 2.3开始,MRO算法已经得到了改进,在复杂的情况下工作得很好,但我猜使用“深度优先的从左到右遍历”+“删除除最后一个重复项之外的重复项”在大多数情况下仍然有效(如果不是这样,请评论)。一定要阅读Guido的博客文章!

其他回答

这就是所谓的钻石问题,该页面有一个关于Python的条目,但简而言之,Python将从左到右调用超类的方法。

也许还可以添加一些东西,比如Django rest_framework和装饰器的一个小例子。这为隐含的问题提供了答案:“我为什么想要这个?”

如前所述:我们使用Django rest_framework,我们使用泛型视图,对于数据库中的每种类型的对象,我们发现我们有一个视图类为对象列表提供GET和POST,另一个视图类为单个对象提供GET、PUT和DELETE。

现在我们要用Django的login_required来装饰POST、PUT和DELETE。注意,这涉及到两个类,而不是两个类中的所有方法。

解决方案可以通过多重继承。

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required

class LoginToPost:
    @method_decorator(login_required)
    def post(self, arg, *args, **kwargs):
        super().post(arg, *args, **kwargs)

其他方法也是如此。

在我的具体类的继承列表,我将添加我的LoginToPost之前ListCreateAPIView和LoginToPutOrDelete之前RetrieveUpdateDestroyAPIView。我的具体类的get将保持未修饰。

我想用“无生命”来详细说明这个答案,因为当我开始阅读如何在Python的多重继承层次结构中使用super()时,我并没有立即得到它。

你需要了解的是super(MyClass, self).__init__()在完整继承层次结构的上下文中根据所使用的方法解析排序(MRO)算法提供下一个__init__方法。

理解这最后一部分至关重要。让我们再考虑一下这个例子:

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

根据Guido van Rossum关于方法解析顺序的文章,解析__init__的顺序是使用“深度优先的从左到右遍历”来计算的(在Python 2.3之前):

Third --> First --> object --> Second --> object

删除所有重复项后,除了最后一个,我们得到:

Third --> First --> Second --> object

那么,让我们来看看当我们实例化一个Third类的实例时会发生什么,例如x = Third()。

According to MRO Third.__init__ executes. prints Third(): entering then super(Third, self).__init__() executes and MRO returns First.__init__ which is called. First.__init__ executes. prints First(): entering then super(First, self).__init__() executes and MRO returns Second.__init__ which is called. Second.__init__ executes. prints Second(): entering then super(Second, self).__init__() executes and MRO returns object.__init__ which is called. object.__init__ executes (no print statements in the code there) execution goes back to Second.__init__ which then prints Second(): exiting execution goes back to First.__init__ which then prints First(): exiting execution goes back to Third.__init__ which then prints Third(): exiting

这详细说明了为什么实例化Third()会导致:

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

从Python 2.3开始,MRO算法已经得到了改进,在复杂的情况下工作得很好,但我猜使用“深度优先的从左到右遍历”+“删除除最后一个重复项之外的重复项”在大多数情况下仍然有效(如果不是这样,请评论)。一定要阅读Guido的博客文章!

Guido在他的博客文章Method Resolution Order(包括两个早期的尝试)中对此进行了详细的描述。

在你的例子中,Third()将调用First.__init__。Python按照从左到右列出的顺序在类的父类中查找每个属性。在本例中,我们正在寻找__init__。如果你定义

class Third(First, Second):
    ...

Python将首先查看First,如果First没有该属性,那么它将查看Second。

当继承开始交叉路径时(例如First从Second继承),这种情况会变得更加复杂。阅读上面的链接了解更多细节,但是,简而言之,Python将试图保持每个类在继承列表中出现的顺序,从子类本身开始。

例如,如果你有:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

MRO为[第四、第二、第三、第一]。

顺便说一下:如果Python无法找到一致的方法解析顺序,它将引发异常,而不是退回到可能使用户感到惊讶的行为。

模棱两可的MRO示例:

class First(object):
    def __init__(self):
        print "first"
        
class Second(First):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        print "third"

Third的MRO是[First, Second]还是[Second, First]?没有明显的期望,Python将引发一个错误:

TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution order (MRO) for bases Second, First

为什么上面的例子没有super()调用?这些示例的重点是展示MRO是如何构造的。他们不打算打印“第一\第二\第三”或其他什么。当然,您可以(也应该)尝试一下这个示例,添加super()调用,看看会发生什么,并更深入地理解Python的继承模型。但我的目标是保持简单,并展示MRO是如何构建的。正如我解释的那样:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)

考虑子AB,父A和B在它们的构造函数中有关键字参数。

  A    B
   \  /
    AB

要初始化AB,需要显式调用父类构造函数,而不是使用super()。

例子:

class A():
    def __init__(self, a="a"):
        self.a = a
        print(f"a={a}")
    
    def A_method(self):
        print(f"A_method: {self.a}")

class B():
    def __init__(self, b="b"):
        self.b = b
        print(f"b={b}")
    
    def B_method(self):
        print(f"B_method: {self.b}")
    
    def magical_AB_method(self):
        print(f"magical_AB_method: {self.a}, {self.b}")

class AB(A,B):
    def __init__(self, a="A", b="B"):
        # super().__init__(a=a, b=b) # fails!
        A.__init__(self, a=a)
        B.__init__(self, b=b)
        self.A_method()
        self.B_method()
        self.magical_AB_method()


A()
>>> a=a

B()
>>> b=b

AB()
>>> a=A
>>> b=B
>>> A_method: A
>>> B_method: B

为了演示两个父类被组合到子类中,请考虑在类B中定义的magical_AB_method。当从B的实例调用时,该方法失败,因为它不能访问A中的成员变量。然而,当从子类AB的实例调用时,该方法工作,因为它从A继承了所需的成员变量。

B().magical_AB_method()
>>> AttributeError: 'B' object has no attribute 'a'

AB().magical_AB_method()
>>> magical_AB_method: A, B