多重继承与使用基类中的方法

8 投票
3 回答
5480 浏览
提问于 2025-04-16 22:23

我有以下代码:

class A(object):
    def __init__(self):
        self.name = "A"
        super(A, self).__init__()

    def Update(self):
        print "Update A"
        self.PickTarget()

    def PickTarget(self):
        print "PickTarget A"

class B(object):
    def __init__(self):
        self.name = "B"
        super(B, self).__init__()

    def Update(self):
        print "Update B"
        self.PickTarget()

    def PickTarget(self):
        print "PickTarget B"

class C(A, B):
    def __init__(self):
        super(C, self).__init__()

    def Update(self, useA):
        if useA:
            A.Update(self)
        else:
            B.Update(self)

c = C()

c.Update(useA = True)
# prints: 
# Update A
# PickTarget A

c.Update(useA = False)
# prints:
# Update B
# PickTarget A

为什么在调用 C.Update 时,即使 useA=False,还是会调用 A.PickTarget?我该怎么做才能让它按照我想要的方式工作(也就是说,B.Update 总是调用 B.PickTarget)?我相信这个问题以前有人问过,但我搜索了也没找到答案——可能是因为我不知道该搜索什么。

3 个回答

0

这不是解决方案,但如果你把C的更新方法改成:

def update(self, useA):
    if useA:
        A().update()
    else:
        B().update()

这样就能按你想要的那样工作了。

你应该了解一下Python的“方法解析顺序”,这样才能更好地处理类似的事情。

4

每当你调用一个对象的方法时,Python会使用“方法解析顺序”(MRO)来决定调用哪个版本的方法。在这个例子中,虽然你明确调用了 A.Update(),但 A.Update() 并没有直接调用 A.PickTarget。它只是调用了 self.PickTarget()。因为这是一个 C 对象,所以这相当于 C.PickTarget(self)C.PickTarget() 是继承来的,而 C 的 MRO 规定在这种情况下要使用 A.PickTarget 这个版本的 PickTarget

你可以这样查看 C 的 MRO:

>>> C.__mro__
(<class '__main__.C'>, <class 'foo.A'>, <class 'foo.B'>, <type 'object'>)

关于 MRO,有一篇非常有用的文章可以在 这里找到。


至于如何实现你想要的行为——其实有很多明显的方法,但同时也没有什么好的方法(我想不出来)。我觉得这并不是一个好的设计。多重继承的主要目的是让你可以在 C 中混合使用不同的方法,但你却试图把来自 AB 的多个相似方法塞进一个类里。如果你能多告诉我们一些你在做什么,也许我们可以建议一个更好的解决方案。(你还在保持相同名称的同时改变了方法的签名,这看起来也不太妥。)

不过,如果你确定想这么做,另一种你可以考虑的方法是 名称重整,这正是为这种情况设计的。它可以完全实现你想要的效果:

class A(object):
    def __init__(self):
        self.name = "A"
        super(A, self).__init__()

    def Update(self):
        print "Update A"
        self.__PickTarget()

    def PickTarget(self):
        print "PickTarget A"

    __PickTarget = PickTarget

class B(object):
    def __init__(self):
        self.name = "B"
        super(B, self).__init__()

    def Update(self):
        print "Update B"
        self.__PickTarget()

    def PickTarget(self):
        print "PickTarget B"

    __PickTarget = PickTarget

class C(A, B):
    def __init__(self):
        super(C, self).__init__()

    def Update(self, useA):
        if useA:
            A.Update(self)
        else:
            B.Update(self)

输出:

>>> from mangling import A, B, C
>>> c = C()
>>> c.Update(useA = True)
Update A
PickTarget A
>>> c.Update(useA = False)
Update B
PickTarget B
7

这是因为在类 C 的基类中,AB 之前。

你需要在 B.Update(self) 中使用 B.PickTarget(self),而不是 self.PickTarget(),这样才能实现你想要的效果。否则,你可以在 C 的定义中把 AB 交换位置。

编辑:

如果你希望 B 总是调用 B 中的方法,而 A 总是调用 A 中的方法,那么使用 A.method(self)正确的,因为 self.method() 这种写法并不能说明 method 是在 A 中的。

你应该重新设计你的类。A 应该有一个移动方法,让机器人随机移动,并定义它的其他基本行为。B 应该是 A 的子类,并且应该有一个移动方法,如果没有路径,就调用 super(B, self).move(),否则就沿着路径移动。这是根据条件重写方法的正确方式。

撰写回答