在Python中使用字段解决钻石问题的最佳方法

问题描述 投票:0回答:1

如果类中没有字段,Python 通过线性化方法解析顺序很好地解决了菱形问题。但是,如果类有字段,那么如何调用超级构造函数呢?考虑:

class A:
    def __init__(self, a):
        self.a = a  # Should only be initialized once.

class B(A):
    def __init__(self, a, b):
        super().__init__(a)
        self.b = b

class C(A):
    def __init__(self, a, c, b=None):
        super().__init__(a)
        self.c = c

class D(C, B):
    def __init__(self, a, b, c):
        super().???  # What do you put in here.

对于我的用例,我实际上有一个解决方案,因为

b
在应用程序中不能是
None
,因此以下内容在很大程度上有效:

class A:
    def __init__(self, a):
        self.a = a  # Should only be initialized once.

class B(A):
    def __init__(self, a, b):
        assert b is not None  # Special case of `b` can't be `None`.
        super().__init__(a)
        self.b = b

class C(A):
    def __init__(self, a, c, b=None):  # Special init with default sentinel `b`.
        if b is None:
            super().__init__(a)  # Normally `C`'s super is `A`.
        else:
            super().__init__(a, b)  # From `D` though, `C`'s super is `B`.
        self.c = c

class D(C, B):  # Note order, `C`'s init is super init.
    def __init__(self, a, b, c):
        super().__init__(a, c, b)

def main():
    A('a')
    B('b', 1)
    C('c', 2)
    D('d', 3, 4)
    C('c2', 5, 6)  # TypeError: __init__() takes 2 positional arguments but 3 were given

这在很大程度上适用于

b
不能是
None
的特殊情况,但是如果直接调用
C
__init__
仍然有问题(参见上面的最后一行)。另外,您必须修改
C
以实现多重继承,并且必须按照
C, B
的顺序继承。

====编辑===

另一种可能性是手动初始化每个字段(这有点类似于 Scala 在幕后处理字段的方式)。

class A0:
    def __init__(self, a):  # Special separate init of `a`.
        self._init_a(a)

    def _init_a(self, a):
        self.a = a


class B0(A0):
    def __init__(self, a, b):  # Special separate init of `b`.
        self._init_a(a)
        self._init_b(b)

    def _init_b(self, b):
        self.b = b


class C0(A0):
    def __init__(self, a, c):  # Special separate init of `c`.
        self._init_a(a)
        self._init_c(c)

    def _init_c(self, c):
        self.c = c

class D0(C0, B0):
    def __init__(self, a, b, c):  # Uses special separate inits of `a`, `b`, and `c`.
        self._init_a(a)
        self._init_b(b)
        self._init_c(c)

这种方法的缺点是非常不标准,以至于 PyCharm 会给出关于不调用 super init 的警告。

====结束编辑===

有更好的方法吗?

提前感谢您的帮助,霍华德。

python python-3.x multiple-inheritance diamond-problem
1个回答
0
投票

@rhettinger 有一篇出色的文章 https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ 有答案。 @rhettinger 代码是一种棘手的代码,它使用与该特定类相关的名称来匹配关键字参数,并将其余部分传递给超类。请参阅文章了解完整说明。

因此,用字段编码钻石问题的“Pythonic”方法是:

class A1:
    def __init__(self, *, a):  # Will fails if anything other than arg `a` is passed on to it.
        self.a = a

    def __repr__(self):
        return f'{type(self).__name__}(**{self.__dict__})'


class B1(A1):
    def __init__(self, *, b, **kwargs):  # Extracts arg `b` and passes others on to other supers.
        self.b = b
        super().__init__(**kwargs)


class C1(A1):
    def __init__(self, *, c, **kwargs):  # Extracts arg `c` and passes others on to other supers.
        self.c = c
        super().__init__(**kwargs)


class D1(C1, B1):  # Note order, C1's init is 1st super init.
    def __init__(self, **kwargs):  # Passes on to supers all arguments.
        super().__init__(**kwargs)


def main1():
    print(A1(a='a'))
    print(B1(a='b', b=1))
    print(C1(a='c', c=2))
    print(D1(a='d', b=3, c=4))


if __name__ == '__main__':
    main1()

以上打印:

A1(**{'a': 'a'})
B1(**{'b': 1, 'a': 'b'})
C1(**{'c': 2, 'a': 'c'})
D1(**{'c': 4, 'b': 3, 'a': 'd'})
© www.soinside.com 2019 - 2024. All rights reserved.