如果类中没有字段,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 的警告。
====结束编辑===
有更好的方法吗?
提前感谢您的帮助,霍华德。
@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'})