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 3.5+中,继承看起来是可预测的,对我来说非常好。 请看下面的代码:

class Base(object):
  def foo(self):
    print("    Base(): entering")
    print("    Base(): exiting")


class First(Base):
  def foo(self):
    print("   First(): entering Will call Second now")
    super().foo()
    print("   First(): exiting")


class Second(Base):
  def foo(self):
    print("  Second(): entering")
    super().foo()
    print("  Second(): exiting")


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


class Fourth(Third):
  def foo(self):
    print("Fourth(): entering")
    super().foo()
    print("Fourth(): exiting")

Fourth().foo()
print(Fourth.__mro__)

输出:

Fourth(): entering
 Third(): entering
   First(): entering Will call Second now
  Second(): entering
    Base(): entering
    Base(): exiting
  Second(): exiting
   First(): exiting
 Third(): exiting
Fourth(): exiting
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

正如你所看到的,它对每个继承链调用foo一次,其顺序与继承链的顺序相同。你可以通过调用.mro来获得订单:

Fourth -> Third -> First -> Second -> Base ->对象

其他回答

考虑从子类调用super(). foo()。方法解析顺序(MRO)方法是解析方法调用的顺序。

案例1:单继承

在这种情况下,super(). foo()将在层次结构中向上搜索,并将考虑最接近的实现,如果找到,否则引发异常。在任何被访问的子类和它在层次结构上的超类之间,“is a”关系将始终为True。但是这个故事在多重继承中并不总是一样的。

案例2:多重继承

在这里,当搜索super(). foo()实现时,层次结构中每个被访问的类都可能是一个关系,也可能不是。考虑以下例子:

class A(object): pass
class B(object): pass
class C(A): pass
class D(A): pass
class E(C, D): pass
class F(B): pass
class G(B): pass
class H(F, G): pass
class I(E, H): pass

这里,I是层次结构中最低的类。层次图和MRO为我将

(红色数字为MRO)

MRO是I E C D A H F G B对象

注意,类X只有在继承自它的所有子类都被访问过的情况下才会被访问。例如,如果一个类下面有一个箭头,而你还没有访问过这个箭头,那么你永远不应该访问这个类)。

在这里,注意在访问类C之后,D被访问,尽管C和D DO NOT have是它们之间的关系(但它们都与a有关系)。这是super()不同于单一继承的地方。

考虑一个稍微复杂一点的例子:

(红色数字为MRO)

MRO是I E C H D A F G B对象

在这种情况下,我们从I到E再到c。下一步是A,但我们还没有访问A的子类D。然而,我们不能访问D,因为我们还没有访问D的子类H。剩下H作为下一个要访问的类。记住,如果可能的话,我们试图在层次结构中向上,所以我们访问它最左边的超类D。在D之后,我们访问A,但我们不能向上到object,因为我们还没有访问F、G和b。这些类,依次,为I完成MRO。

注意,任何类都不能在MRO中出现超过一次。

这就是super()在继承层次结构中的查找方式。

资源来源:Richard L Halterman Python编程基础

我想用“无生命”来详细说明这个答案,因为当我开始阅读如何在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的博客文章!

也许还可以添加一些东西,比如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将保持未修饰。

另一个尚未涉及的点是传递初始化类的参数。由于super的目标取决于子类,传递参数的唯一好方法是将它们打包在一起。然后注意不要让相同的参数名具有不同的含义。

例子:

class A(object):
    def __init__(self, **kwargs):
        print('A.__init__')
        super().__init__()

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

给:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

直接调用超类__init__来更直接地赋值参数是很诱人的,但如果在超类中有任何超调用和/或MRO被更改并且类a可能被多次调用,则会失败,这取决于实现。

总结一下:合作继承和初始化的超参数和特定参数不能很好地协同工作。

在这种情况下,你试图继承的每个类都有自己的init位置参数,只需调用每个类自己的init方法,如果试图继承多个对象,则不要使用super。

class A():
    def __init__(self, x):
        self.x = x

class B():
    def __init__(self, y, z):
        self.y = y
        self.z = z

class C(A, B):
    def __init__(self, x, y, z):
        A.__init__(self, x)
        B.__init__(self, y, z)

>>> c = C(1,2,3)
>>>c.x, c.y, c.z 
(1, 2, 3)