Python中每个祖先的调用方法

时间:2010-01-01 22:23:40

标签: python datamodel

我在python中有一个类'D'的对象,我想按顺序执行'D'定义的'run'方法及其每个祖先('A','B'和'C')

我能够像这样完成这个

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

所以我的问题是:

  1. 为什么不工作?

  2. 这是最好的解决方法吗?

3 个答案:

答案 0 :(得分:4)

如果您可以更改所有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

run B版本的D有两个“断断续续”,AC继承而不会覆盖(分别来自superrun_all)。假设我是正确的您想要的效果,如果您希望避免def run_all(self): for cls in reversed(self.__class__.__mro__): meth = cls.__dict__.get('run') if meth is not None: meth(self) ,可以尝试将def更改为:

run

AC中替换为Running A Running C 的两个不同in的最新示例,使示例打印:

None

我怀疑它可能更接近你想要的东西。

还有一点要点:不要重复工作 - 监视getattr,或者getattr测试保护dict访问 - 守卫检查和守卫访问者必须重复完全相同在内部工作,没有好的目的。相反,使用get的第三个参数到单个None调用(或dict的get方法):这意味着如果方法不存在,您将检索{{{ 1}}值,然后您可以针对该事件保护调用。这正是dicts具有getattr方法和{{1}}具有第三个可选“默认”参数的原因:为了便于应用DRY,“不要重复自己”,这是一个非常重要的格言好的节目! - )

答案 1 :(得分:1)

您不应该使用__getattribute__方法..

只需执行以下操作:

getattr(cls, 'run')(self)

答案 2 :(得分:1)

为什么不简单地使用super?虽然有些consider it harmful,但它的设计考虑到了这种情况,我会毫不犹豫地使用它。

来自Python documentation

  

这对于访问继承有用   已被覆盖的方法   类。搜索顺序与   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()