多重继承与使用基类中的方法
我有以下代码:
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 个回答
这不是解决方案,但如果你把C的更新方法改成:
def update(self, useA):
if useA:
A().update()
else:
B().update()
这样就能按你想要的那样工作了。
你应该了解一下Python的“方法解析顺序”,这样才能更好地处理类似的事情。
每当你调用一个对象的方法时,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
中混合使用不同的方法,但你却试图把来自 A
和 B
的多个相似方法塞进一个类里。如果你能多告诉我们一些你在做什么,也许我们可以建议一个更好的解决方案。(你还在保持相同名称的同时改变了方法的签名,这看起来也不太妥。)
不过,如果你确定想这么做,另一种你可以考虑的方法是 名称重整,这正是为这种情况设计的。它可以完全实现你想要的效果:
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
这是因为在类 C
的基类中,A
在 B
之前。
你需要在 B.Update(self)
中使用 B.PickTarget(self)
,而不是 self.PickTarget()
,这样才能实现你想要的效果。否则,你可以在 C
的定义中把 A
和 B
交换位置。
编辑:
如果你希望 B
总是调用 B
中的方法,而 A
总是调用 A
中的方法,那么使用 A.method(self)
是正确的,因为 self.method()
这种写法并不能说明 method
是在 A
中的。
你应该重新设计你的类。A
应该有一个移动方法,让机器人随机移动,并定义它的其他基本行为。B
应该是 A
的子类,并且应该有一个移动方法,如果没有路径,就调用 super(B, self).move()
,否则就沿着路径移动。这是根据条件重写方法的正确方式。