【问题标题】:How does Python pass __init__ parameters with multiple inheritancePython如何通过多重继承传递__init__参数
【发布时间】:2018-10-16 14:48:44
【问题描述】:

我有这个代码,显示了一个经典的菱形图案:

class A:
    def __init__( self, x ):
        print( "A:" + x )


class B( A ):
    def __init__( self, x ):
        print( "B:" + x )
        super().__init__( "b" )


class C( A ):
    def __init__( self, x ):
        print( "C:" + x )
        super().__init__( "c" )


class D( B, C ):
    def __init__( self ):
        super().__init__( "d" )


d = D()

输出是:

B:d
C:b
A:c
  • B:d 是有道理的,因为 D 派生自 B
  • A:c 我几乎得到了,虽然我同样可以看到A:b
  • 但是,C:b 位没有意义:C 不是从 B 派生的。

谁能解释一下?

this之类的问题很遗憾没有提到参数。

【问题讨论】:

  • “不幸的是,像这样的问题没有提到参数。”请注意,self 也是一个参数!它的处理方式与其他任何操作一样。

标签: python python-3.x multiple-inheritance


【解决方案1】:

Python 使用C3 linearization algorithm 建立方法解析顺序,与super 委托的顺序相同。

基本上,该算法为包含该类的每个类及其继承自的每个类以及相关类继承自的所有类保留列表。然后,它通过一个接一个地获取未被任何未检查的类继承的类,直到到达根object,来构造类的顺序。下面,为了简洁起见,我使用O 表示object

L(O) = [O]

L(A) = [A] + merge(L(O), [O]) = [A, O]

L(B) = [B] + merge(L(A), [A]) = [B] + merge([A, O], [A]) = [B, A] + merge([O]) 
     = [B, A, O]

L(C) = [C] + merge(L(A), [A]) = [C] + merge([A, O], [A]) = [C, A] + merge([O]) 
     = [C, A, O]

L(D) = [D] + merge(L(B), L(C), [B, C]) = [D] + merge([B, A, O], [C, A, O], [B, C])
     = [D, B] + merge([A, O], [C, A, O], [C]) = [D, B, C] + merge([A, O], [A, O])
     = [D, B, C, A, O]

【讨论】:

    【解决方案2】:

    Python 中的类是动态组合的——包括继承。

    C:b 输出并不意味着B 神奇地继承自C。如果您实例化BC,则没有人知道另一个。

    >>> B('root')
    B:root
    A:b
    

    但是,D 确实知道BC

    class D(B,C):
        ...
    

    这里有很多technicalities。但是,它的工作原理基本上有两个部分:

    1. 直接基类按照它们出现的顺序进行解析。
      • B 位于 C 之前。
    2. 递归基类被解析为不重复。
      • BC 的基类必须跟在两者之后。

    对于D 类,这意味着基类解析为B->C->AC 潜入 BA 之间 - 但仅限于 D 类,不适用于 B 类。


    请注意,实际上还涉及另一个类:默认情况下,所有类都派生自object

    >>> D.__mro__
    (__main__.D, __main__.B, __main__.C, __main__.A, object)
    

    你已经写了A,知道没有基础来获取它的参数。但是,BC 都不能假设这一点。他们都希望从A 对象派生。不过,子类化确实意味着BC 也是有效的A-objects!

    B C有效B C 之前,因为两者是A 的子类。 B->C->A->object 不会破坏 B 期望其超类的类型为 A

    对于所有其他组合,以 C 结尾(无效)或 object 开头(无效)。这排除了深度优先分辨率B->A->object->C 和重复B->A->object->C->A->object


    这种方法解析顺序对于启用 mixins 很实用:依赖其他类来定义方法解析方式的类。

    有一个nice example 说明用于字典访问的记录器如何同时接受dictOrderedDict

    # basic Logger working on ``dict``
    class LoggingDict(dict):
        def __setitem__(self, key, value):
            logging.info('Settingto %r' % (key, value))
            super().__setitem__(key, value)
    
    # mixin of different ``dict`` subclass
    class LoggingOD(LoggingDict, collections.OrderedDict):
        pass
    

    【讨论】:

      【解决方案3】:

      您始终可以检查任何类应具有的方法解析顺序:

       >>> D.mro()
       [__main__.D, __main__.B, __main__.C, __main__.A, object]
      

      如您所见,如果每个人都在做正确的事情(即调用 super),MRO 将是第一个父母、第二个父母、第一个父母的父母等等......

      您可以先考虑深度,然后从左到右查找顺序,尽管自 python 2.3 以来算法发生了变化,但结果与 usually 相同。

      在这种情况下,B 和 C 具有相同的父 A,而 A 不调用 super

      【讨论】:

        猜你喜欢
        • 2018-05-25
        • 2015-05-04
        • 2019-09-25
        • 2012-01-31
        • 1970-01-01
        • 2020-12-19
        • 2014-12-25
        • 2020-07-05
        • 2012-06-26
        相关资源
        最近更新 更多