Python - 如何避免调用祖先构造函数(多继承)

4

BC 继承自类 A。类 D 同时从 BC 继承:

class A:
    def __init__(self):
        print('A')

class B(A):
    def __init__(self):
        A.__init__(self)
        print('B')

class C(A):
    def __init__(self):
        A.__init__(self)
        print('C')

class D(B, C):
    def __init__(self):
        B.__init__(self)
        C.__init__(self)
        print('D')

d = D()

这将输出:
A
B
A
C
D

有没有什么方法可以避免调用构造函数 A 两次?
注意:在实际情况中,BC__init__ 方法没有相同的签名,因此使用 super() 不是一个选择(就我所知...)。
干杯!

1
你控制B和C吗?最好的方法是始终接受*args, **kwargs,提取每个类需要的参数,并将其余部分传递给super。 - Daniel Roseman
我确实对它们有控制权,使用 *args**kwargs 是个好主意,但说实话,使用 super 有点让我感到害怕,因为我并没有深入理解它的真正行为。 - Niourf
1
super() 不要求所调用的方法具有相同的签名。 - martineau
1
那么你应该阅读 Raymond Hettinger 的 Super considered super - Daniel Roseman
你正在使用Python 2还是3?这很重要,因为在Python 2中,类A将是旧式类,因为它没有明确地派生自object - 新式类具有不同的MRO(方法解析顺序)。 - martineau
2个回答

3

如果您知道 A 将在多个基类中使用,您可以让它自行检测双重初始化。

class A:
    def __init__(self):
        if hasattr(self, '_initialized'):
            return
        print('A')
        self._initialized = true

3
这绝对是使用 super 的情况。您可以使用 *args 和 **kwargs 控制每个级别上的参数分派方式。需要注意的是,类声明中基类的顺序控制了 super 在查找下一个方法时搜索类的顺序。因此,它还将控制位置参数被消耗的顺序。
例如:
class A:
    def __init__(self, a, *, caller):
        print("A.__init__(a={!r}) called by {}".format(a, caller))

class B(A):
    def __init__(self, b, *args, caller, **kwargs):
        super().__init__(caller="B", *args, **kwargs )
        print("B.__init__(b={!r}) called by {}".format(b, caller))

class C(A):
    def __init__(self, c, *args, caller, **kwargs):
        super().__init__(caller="C", *args, **kwargs)
        print("C.__init__(c={!r}) called by {}".format(c, caller))

class D(B, C):
    def __init__(self, d, *args, **kwargs):
        super().__init__(caller="D", *args, **kwargs)
        print("D.__init__(d={!r}) called by {}".format(d, "user"))

try:
    print("too many arguments")
    D(a=1, b=2, c=3, d=4, e=5)
except TypeError as e:
    print(e)
try:
    print("too few arguments")
    D(a=1, c=3, d=4)
except TypeError as e:
    print(e)
print("using keyword args")
D(a=1, b=2, c=3, d=4)
print("using positional args")
D(4, 2, 3, 1) # as B comes before C, B's argument must come before C's
print("using a mix of positional and keyword args")
D(4, 2, a=1, c=3)

其输出结果为:
too many arguments
__init__() got an unexpected keyword argument 'e'
too few arguments
__init__() missing 1 required positional argument: 'b'
using keyword args
A.__init__(a=1) called by C
C.__init__(c=3) called by B
B.__init__(b=2) called by D
D.__init__(d=4) called by user
using positional args
A.__init__(a=1) called by C
C.__init__(c=3) called by B
B.__init__(b=2) called by D
D.__init__(d=4) called by user
using a mix of positional and keyword args
A.__init__(a=1) called by C
C.__init__(c=3) called by B
B.__init__(b=2) called by D
D.__init__(d=4) called by user

如果您不确定基类搜索的顺序,请使用D.mro()mro代表方法解析顺序。 它返回一个类列表,其中属性(在我们的情况下是__init__)将被搜索。

>>> print(D.mro())
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, 
        <class 'object'>]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接