8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

Python 的 super() 如何与多重继承一起工作?

peterjwest 1月前

169 0

super() 如何与多重继承一起工作?例如,给定:class First(object): def __init__(self): print \'first\'class Second(object): def __init__(self): ...

如何 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__ ?我可以选择运行哪个吗?

有关 MRO

帖子版权声明 1、本帖标题:Python 的 super() 如何与多重继承一起工作?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由peterjwest在本站《oop》版块原创发布, 转载请注明出处!
最新回复 (0)
  • Quan 1月前 0 只看Ta
    引用 2

    是的。这就是为什么我写了 A is object 或许我认为我应该写 class A (object) :

  • A 也应该调用 __init__。A 并没有“发明”方法 __init__,因此它不能假设其他类可能在其 MRO 中更早地拥有 A。唯一一个 __init__ 方法不会(也不应该)调用 super().__init__ 的类是 object。

  • 发布此答案以供我将来参考。

    Python 多重继承应该使用菱形模型,并且函数签名在模型中不应该改变。

        A
       / \
      B   C
       \ /
        D
    

    示例代码片段如下:

    class A:
        def __init__(self, name=None):
            #  this is the head of the diamond, no need to call super() here
            self.name = name
    
    class B(A):
        def __init__(self, param1='hello', **kwargs):
            super().__init__(**kwargs)
            self.param1 = param1
    
    class C(A):
        def __init__(self, param2='bye', **kwargs):
            super().__init__(**kwargs)
            self.param2 = param2
    
    class D(B, C):
        def __init__(self, works='fine', **kwargs):
            super().__init__(**kwargs)
            print(f"{works=}, {self.param1=}, {self.param2=}, {self.name=}")
    
    d = D(name='Testing')
    

    这里 A 类 object

  • 也许还可以添加一些内容,比如一个带有 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 before ListCreateAPIView LoginToPutOrDelete before RetrieveUpdateDestroyAPIView 。 我的具体类 get 将保持未修饰状态。

  • 在学习pythonthehardway时,我学到了一个叫做super()的东西,如果没有记错的话,这是一个内置函数。调用super()函数可以帮助继承通过父级和“兄弟”传递,并帮助您看得更清楚。我还是个初学者,但我很乐意分享我在python2.7中使用这个super()的经验。

    如果你读过本页的评论,你就会知道方法解析顺序 (MRO),方法是你编写的函数,MRO 将使用深度优先从左到右的方案进行搜索和运行。你可以对此进行更多研究。

    通过添加 super() 函数

    super(First, self).__init__() #example for class First.
    

    您可以使用 super() 连接多个实例和“家族”,方法是添加其中的每一个。它将执行方法,检查它们,并确保您没有遗漏!但是,在之前或之后添加它们确实会产生影响,如果您已经完成了 learningpythonthehardway 练习 44,您就会知道。让乐趣开始吧!!

    以下面的例子为例,您可以复制粘贴并尝试运行它:

    class First(object):
        def __init__(self):
    
            print("first")
    
    class Second(First):
        def __init__(self):
            print("second (before)")
            super(Second, self).__init__()
            print("second (after)")
    
    class Third(First):
        def __init__(self):
            print("third (before)")
            super(Third, self).__init__()
            print("third (after)")
    
    
    class Fourth(First):
        def __init__(self):
            print("fourth (before)")
            super(Fourth, self).__init__()
            print("fourth (after)")
    
    
    class Fifth(Second, Third, Fourth):
        def __init__(self):
            print("fifth (before)")
            super(Fifth, self).__init__()
            print("fifth (after)")
    
    Fifth()
    

    它是如何运行的?fifth() 的实例将像这样运行。每一步都从一个类到另一个类,其中添加了超级函数。

    1.) print("fifth (before)")
    2.) super()>[Second, Third, Fourth] (Left to right)
    3.) print("second (before)")
    4.) super()> First (First is the Parent which inherit from object)
    

    已找到父级并且将继续进行第三和第四次!!

    5.) print("third (before)")
    6.) super()> First (Parent class)
    7.) print ("Fourth (before)")
    8.) super()> First (Parent class)
    

    现在所有带有 super() 的类都已被访问!父类已被找到并执行,现在它继续在继承中解包函数以完成代码。

    9.) print("first") (Parent)
    10.) print ("Fourth (after)") (Class Fourth un-box)
    11.) print("third (after)") (Class Third un-box)
    12.) print("second (after)") (Class Second un-box)
    13.) print("fifth (after)") (Class Fifth un-box)
    14.) Fifth() executed
    

    上述程序的结果:

    fifth (before)
    second (before
    third (before)
    fourth (before)
    first
    fourth (after)
    third (after)
    second (after)
    fifth (after)
    

    对于我来说,通过添加 super() 可以让我更清楚地了解 python 如何执行我的编码,并确保继承可以访问我想要的方法。

  • 尾部位置指的是类层次结构中较高的类,反之亦然。基类“object”位于尾部的末尾。理解 mro 算法的关键是“Second”如何作为“First”的超类出现。我们通常会假设它是“object”类。这是真的,但是,仅从“First”类的角度来看。然而,从“Third”类的角度来看,“First”的层次顺序是不同的,并且按上面所示计算。mro 算法尝试为所有多个继承的类创建这个视角(或层次子集)

  • \'而是因为 Second 出现在头部位置,而不是出现在层次结构子集的尾部位置。\' 不清楚头部位置或尾部位置是什么,也不清楚层次结构子集是什么或您指的是哪个子集。

  • 我想补充一下 @Visionscaper 在顶部所说的内容:

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

    在这种情况下,解释器不会因为对象类重复而将其过滤掉,而是因为 Second 出现在层次结构子集的头部位置,而没有出现在尾部位置。而对象仅出现在尾部位置,在 C3 算法中不被视为确定优先级的强势位置。

    C 类的线性化(mro),L(C),是

    • C类
    • 加上合并
      • linearisation of its parents P1, P2, .. = L(P1, P2, ...) and
      • the list of its parents P1, P2, ..

    线性合并是通过选择出现在列表头部而不是尾部的公共类来完成的,因为顺序很重要(下面会讲清楚)

    第三个线性化可以按如下方式计算:

        L(O)  := [O]  // the linearization(mro) of O(object), because O has no parents
    
        L(First)  :=  [First] + merge(L(O), [O])
                   =  [First] + merge([O], [O])
                   =  [First, O]
    
        // Similarly, 
        L(Second)  := [Second, O]
    
        L(Third)   := [Third] + merge(L(First), L(Second), [First, Second])
                    = [Third] + merge([First, O], [Second, O], [First, Second])
    // class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists
    // class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, 
                    = [Third, First] + merge([O], [Second, O], [Second])
    // class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3
                    = [Third, First, Second] + merge([O], [O])            
                    = [Third, First, Second, O]
    

    因此对于以下代码中的 super() 实现:

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

    这种方法如何解决变得显而易见

    Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
    Object.__init__() ---> returns ---> Second.__init__() -
    prints "second" - returns ---> First.__init__() -
    prints "first" - returns ---> Third.__init__() - prints "that's it"
    
  • class First(object):
      def __init__(self, a):
        print "first", a
        super(First, self).__init__(20)
    
    class Second(object):
      def __init__(self, a):
        print "second", a
        super(Second, self).__init__()
    
    class Third(First, Second):
      def __init__(self):
        super(Third, self).__init__(10)
        print "that's it"
    
    t = Third()
    

    输出为

    first 10
    second 20
    that's it
    

    调用 Third() 可定位 init。MRO 。而在该例程中调用 super 可调用 init =[First, Second]。现在,调用 init 将继续搜索 MRO 并找到 init ,并且任何对 super 的调用都将命中默认对象 init 。我希望这个例子能阐明这个概念。

    如果您没有从 First 调用 super。链将停止,您将获得以下输出。

    first 10
    that's it
    
  • @lousycoder 您可以通过搜索“方法解析顺序”(MRO)来了解相关内容,或者直接查看链接:en.wikipedia.org/wiki/C3_linearization

  • @lousycoder 发生这种情况只是因为 python 阻止了两次调用 \'Base\'

  • 引用 13

    为什么它不遵循这样的顺序:第四 -> 第三 -> 第一 -> 基础 -> 第二 -> 基础?每次方法调用 super 时,它都会转到父类,为什么在 \'First\' 类的情况下不会发生这种情况?

  • 引用 14

    在 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 :

    第四个 -> 第三个 -> 第一 个 -> 第二 个 -> 基础 -> 对象

  • 考虑 child 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()
    
    
    A()
    >>> a=a
    
    B()
    >>> b=b
    
    AB()
    >>> a=A
    >>> b=B
    >>> A_method: A
    >>> B_method: B
    

    为了演示两个父类合并为子类,考虑 magical_AB_method 在类中定义的 B 。当从 的实例调用时 B ,该方法会失败,因为它无法访问 内部的成员变量 A 。但是,当从子类的实例调用时 AB ,该方法可以工作,因为它从 继承了所需的成员变量 A .

    B().magical_AB_method()
    >>> AttributeError: 'B' object has no attribute 'a'
    
    AB().magical_AB_method()
    >>> magical_AB_method: A, B
    
  • 引用 16

    如果您尝试继承的每个类都有自己的 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)
    
  • 我认为如果你的继承层次结构看起来像这样,那么你已经进入并超越了 OO 地狱,哈哈

  • 考虑 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 将 I

    enter image description here

    (红色数字表示 MRO)

    MRO 是 I E C D A H F G B object

    请注意,仅当访问了从该类继承的所有子类时,才会访问该类 X (即,您永远不应该访问具有从您尚未访问过的下面的类进入的箭头的类)。

    这里要注意的是,在访问类之后, C , D 虽然 C D 没有 关系 (但都有 A )。这就是 super() 与单继承不同的地方。

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

    enter image description here

    (红色数字表示 MRO)

    MRO 是 I E C H D A F G B object

    在这种情况下,我们从 I E C 。下一步将是 A ,但我们尚未访问 D ,是的子类 A 。但是,我们不能访问 D ,因为我们尚未访问 H ,是的子类 D 。叶子 H 是下一个要访问的类。请记住,如果可能,我们会尝试在层次结构中向上移动,因此我们访问其最左边的超类 D 访问 D 之后 A ,但我们不能向上移动到对象,因为我们尚未访问 F , G B 。这些类按顺序完成了 MRO 的完成。 I .

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

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

    资料来源:Richard L Halterman 的《Python 编程基础》

  • 另一个尚未涉及的要点是传递用于初始化类的参数。由于目标 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__ 来更直接地分配参数很诱人,但如果 super 超类中有任何调用和/或 MRO 发生变化,则会失败,并且类 A 可能会被调用多次,具体取决于实现。

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

  • @TomBusby:嗯,我同意。理论上,你可以定义 __new__ 并在其中调用 B.__new__(),例如,在 __init__ 中调用 B.__init__()。但这太复杂了……

返回
作者最近主题: