在多重继承设置中传递不同参数的 Pythonic 方式

Pythonic way of passing different arguments in multiple inheritance setup

考虑以下片段:

class A:
    def __init__(self, a):
        self._a = a

class B:
    def __init__(self, b1, b2):
        self._b1 = b1
        self._b2 = b2

class C(B, A):
    def __init__(self, b1, b2, a):
        super().__init__(b1=b1, b2=b2, a=a)

然后会遇到如下错误:

TypeError: init() got an unexpected keyword argument 'a'

为了解决它,我初始化超类'A',如下:

class C(B, A):
    def __init__(self, b1, b2, a):
        super().__init__(b1=b1, b2=b2)
        A.__init__(self, a=a)

另一种解决方案:

class C(B, A):
    def __init__(self, b1, b2, a):
        super().__init__(b1=b1, b2=b2)
        super(B, self).__init__(a=a)

我通过试错法找到了这些解决方案。所以,我想知道将参数传递给多个超类的最优雅的方式是什么。

如果您有 classes 将与他们不需要知道的其他 classes 组成(具体来说,不需要或不关心他们的参数),您可以使用 ** Python Parameter/argument 传递语法将参数折叠和展开到字典中。

换句话说,您的代码如下:

class A:
    def __init__(self, a, **kwargs):
        self._a = a
        super().__init__(**kwargs)

class B:
    def __init__(self, b1, b2, **kwargs):
        self._b1 = b1
        self._b2 = b2
        super().__init__(**kwargs)

class C(B, A):
    def __init__(self, b1, b2, a):
        super().__init__(b1=b1, b2=b2, a=a)

另请注意,在上面的 base-classes __init__ 代码中,您没有调用 super(),所以只是更近的 super-[= 的 __init__ 61=] 将永远被执行。

super() in Python 完成多重继承正常工作所需的所有魔法。最完整的文章之一仍然是 Python's super considered Super

简而言之会发生什么:每当您创建一个 class,使用或不使用多个父级时,Python 都会为其创建一个 __mro__ 属性 - 这就是 "method resolution order" 对于那个 class,表示在祖先中搜索方法和属性的顺序。

计算 MRO 的算法本身很复杂,解释起来有点费劲,但可以合理地信任它 "just does the right thing"。直到今天,我发现它的完整描述的唯一地方是 15 多年前的 its original presentation in Python 2.3 documentation。 (选读,照原样"does the right thing"。)

super() 所做的是创建一个代理对象,它将在 "mro" 序列中选择下一个 class 用于原始调用 class,并直接搜索方法在其 __dict__ - 如果没有找到,它将转到 "mro" 上的下一个条目。 所以,如果你只考虑class B,它体内的super().__init__()会调用object.__init__。如果此时 "kwargs" 为空,就像 B() 仅使用它关心的参数调用时一样,这正是我们想要的。

B.__init__ 在由 "B" 和 [=54= 组成的 class "C" 的链式 super() 调用中 运行 ],无论如何,B.__init__ 中的 super() 将使用 "C" 的 MRO - 而下一个 class 是 "A" - 所以 A.__init__ 是使用所有未使用的关键字参数调用。