【问题标题】:Python "new style" multiple inheritance combined with multiple __init__ argumentsPython“新风格”多重继承结合多个 __init__ 参数
【发布时间】:2020-12-19 11:26:30
【问题描述】:

据我了解,如果所有类都使用“新样式”,那么会发生这种情况:

class A(object):
    def __init__(self):
        print("Entering A")
        super().__init__()
        print("Leaving A")

class B(object):
    def __init__(self):
        print("Entering B")
        super().__init__()
        print("Leaving B")

class C(A, B):
    def __init__(self):
        print("Entering C")
        super().__init__()
        print("Leaving C")

c = C()
Entering C
Entering A
Entering B
Leaving B
Leaving A
Leaving C

在了解 super 使用原始对象 (c) 的 MRO 之后,Entering/Leaving 为何如此是有道理的。但是如果我需要将一些特定的参数传递给AB,而且C中接收参数的顺序与AB不相似:

class A(object):
    def __init__(self, x1, x2):
        print(f"Entering A [x1={x1}, x2={x2}]")
        super().__init__()
        print("Leaving A")

class B(object):
    def __init__(self, y1, y2):
        print(f"Entering B [y1={y1}, y2={y2}]")
        super().__init__()
        print("Leaving B")

class C(A, B):
    def __init__(self, x1, y1, x2, y2):
        print(f"Entering C [x1={x1}, y1={y1}, x2={x2}, y2={y2}]")
        super().__init__()
        print("Leaving C")

c = C('x1', 'y1', 'x2', 'y2')

我可以考虑整天路过kwargs,但我觉得可能有更好的方法。

另外,如果B 是库的一部分,因此我们无法更改它并且它使用位置参数,而不是kwargs,该怎么办?在这种情况下,我该怎么办,因为我认为手动弄乱 MRO 不是一个好主意,但同时B 的构造函数不会采用kwargs

解决上述问题的正确方法是什么?

【问题讨论】:

  • 我很困惑,你想在不知道构造函数前面有哪个父级和什么签名的情况下调用父级的构造函数?如果您需要灵活性,您可以使用kwargs 执行此操作并获得所需的位,但“正确”的方法是调用A.__init__(self, x1, x2)B.__init__(self, y1, y2)。没有魔法,没有歧义。
  • @OndrejK.but 那不是“旧风格”吗?我了解您如何使用该样式,但这意味着“新样式”应该能够完成“旧样式”所能做的所有事情,并在此基础上提供一些额外的好处(在这种情况下,分解代码更健壮)。
  • 对于你所描述的,这不是“旧式”的问题,而是关于设计/意图的explicit
  • @OndrejK。如果它不能执行与“旧样式”相同的任务,您能否解释一下为什么要引入这种“新样式”?

标签: python oop inheritance multiple-inheritance


【解决方案1】:

好的,这将不再适合 cmets,但这里似乎存在一些误解。

为:

class C:
    def __init__(self, arg1=1):
        self.attr1 = arg1

class D(C):
    def __init__(self, arg1):
        super().__init__()

这将产生:

d = D(2)
print(d.attr1)  # prints: 1

因为通过super().__init__()我们调用了parent的构造函数,但是我们没有传递任何参数,它以默认值运行。

或者引用 super 所做的文档:

返回一个代理对象,该对象将方法调用委托给该类型的父类或同级类。这对于访问已在类中覆盖的继承方法很有用。

即您“仅”获取(可能绑定)已解决的方法来调用。不多也不少。它不会整理您传递的任何参数。所以你的例子实际上是行不通的,因为父母的构造函数都需要一个实例和另外两个参数。

在您的示例中,您可以说:

class A:
    def __init__(self, x1, x2):
        print(f"Entering A [x1={x1}, x2={x2}]")
        print("Leaving A")

class B:
    def __init__(self, y1, y2):
        print(f"Entering B [y1={y1}, y2={y2}]")
        print("Leaving B")

class C(A, B):
    def __init__(self, x1, y1, x2, y2):
        print(f"Entering C [x1={x1}, y1={y1}, x2={x2}, y2={y2}]")
        A.__init__(self, x1, x2)
        B.__init__(self, y1, y2)
        print("Leaving C")

因为您使用相应的特定参数调用每个特定的父构造函数。文档(same place,关于具有多重继承的第二个常见用例的几段)也包含有关此的提示:

良好的设计要求此方法在每种情况下都具有相同的调用签名(因为调用的顺序是在运行时确定的,因为该顺序会适应类层次结构的变化,并且因为该顺序可以包括未知的兄弟类运行前)。

也就是说,您获得了一个代理来调用该方法...并且您使用它的参数调用它...这必须是一个固定/稳定的集合,因为您无法预先确定您通过哪个方法到达代理。

【讨论】:

    猜你喜欢
    • 2012-01-31
    • 1970-01-01
    • 1970-01-01
    • 2020-07-05
    • 1970-01-01
    • 2021-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多