在Python中调用每个祖先的方法
我在Python中有一个'D'类的对象,我想依次执行'D'及其祖先类('A'、'B'和'C')定义的'run'方法。
我可以这样做到:
class A(object):
def run_all(self):
# I prefer to execute in revere MRO order
for cls in reversed(self.__class__.__mro__):
if hasattr(cls, 'run'):
# This works
cls.run(self)
# This doesn't
#cls.__getattribute__(self, 'run')()
def run(self):
print "Running A"
class B(A):
def run(self):
print "Running B"
class C(A):
def run(self):
print "Running C"
class D(C, B):
def run(self):
print "Running D"
if __name__ == "__main__":
D().run_all()
这样做的结果是:
$ python test.py
Running A
Running B
Running C
Running D
不过实际上,我并不知道要执行的方法的名字。但是如果我尝试使用getattribute()(见注释的那行),它就不管用了:
$ python test.py
Running D
Running D
Running D
Running D
所以我有几个问题:
为什么这样不行呢?
这样做真的是最好的方法吗?
3 个回答
你为什么不直接用 super
呢?虽然有些人认为它有点问题,但它的设计就是为了应对这种情况,所以我会毫不犹豫地使用它。
根据 Python 的文档:
这个功能很有用,可以用来访问在类中被重写的继承方法。它的搜索顺序和
getattr()
一样,只是跳过了类型本身。 [...] 这使得实现“钻石图”成为可能,也就是多个基类实现同一个方法的情况。
更新:在你的情况下,它会变成这样:
class A(object):
def run(self):
print "Running A"
class B(A):
def run(self):
super(B, self).run()
print "Running B"
class C(A):
def run(self):
super(C, self).run()
print "Running C"
class D(C, B):
def run(self):
super(D, self).run()
print "Running D"
if __name__ == "__main__":
D().run()
你不应该使用 __getattribute__
这个方法。
你只需要这样做:
getattr(cls, 'run')(self)
如果你愿意修改所有的 run
实现(并在 D 中调用 run
而不是 run_all
),这样做是可行的:
class A(object):
def run(self):
print "Running A"
class B(A):
def run(self):
super(B, self).run()
print "Running B"
class C(A):
def run(self):
super(C, self).run()
print "Running C"
class D(C, B):
def run(self):
super(D, self).run()
print "Running D"
if __name__ == "__main__":
D().run()
注意,我在根类中没有使用 super
-- 它“知道”没有更高的父类可以调用(object
并没有定义 run
方法)。不幸的是,在 Python 2 中,这样做会显得有点啰嗦(而且不太适合用装饰器来实现)。
你对 hasattr
的检查有点脆弱,如果我理解你的意图没错的话 -- 如果一个类定义了或继承了某个属性,它就会被认为“拥有”这个属性。所以如果你有一个中间类,它没有重写 run
,但在 __mro__
中出现,那么在你的方法中,它继承的 run
版本会被调用两次。例如,考虑:
class A(object):
def run_all(self):
for cls in reversed(self.__class__.__mro__):
if hasattr(cls, 'run'):
getattr(cls, 'run')(self)
def run(self):
print "Running A"
class B(A): pass
class C(A):
def run(self):
print "Running C"
class D(C, B): pass
if __name__ == "__main__":
D().run_all()
这会打印
Running A
Running A
Running C
Running C
对于 B
和 D
继承但没有重写的 run
版本,会出现两个“重复”。假设我说的没错,这并不是你想要的效果,如果你想避免使用 super
,你可以尝试将 run_all
改成:
def run_all(self):
for cls in reversed(self.__class__.__mro__):
meth = cls.__dict__.get('run')
if meth is not None: meth(self)
这样替换到我最新的例子中,只需在 A
和 C
中有两个不同的 def
定义 run
,就会使例子打印:
Running A
Running C
我怀疑这可能更接近你想要的结果。
还有一点:不要重复工作 -- 使用 hasattr
来保护 getattr
,或者用 in
测试来保护字典访问 -- 保护中的检查和被保护的访问器,内部必须重复完全相同的工作,这样做没有任何好处。相反,使用 getattr
调用的第三个参数 None
(或者字典的 get
方法):这意味着如果方法不存在,你会得到一个 None
值,然后你可以针对这种情况来保护 调用。这正是字典有 get
方法和 getattr
有第三个可选“默认”参数的原因:为了方便应用 DRY 原则,“不要重复自己”,这是良好编程中非常重要的一条原则!-)