2

我在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

所以我的问题是:

  1. 为什么它不起作用?

  2. 这甚至是解决这个问题的最好方法吗?

4

3 回答 3

4

If you're OK with changing all the run implementations (and calling run instead of run_all in D), this works:

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()

Note that I don't use super in the root class -- it "knows" there's no further superclass to go up to (object does not define a run method). Unfortunately, in Python 2, this is inevitably verbose (and not well suited to implementing via a decorator, either).

Your check on hasattr is quite fragile, if I understand your purposes correctly -- it will find that a class "has" the attribute if it defines or inherits it. So if you have an intermediate class that doesn't override run but does occur on the __mro__, the version of run it inherits gets called twice in your approach. E.g., consider:

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()

this prints

Running A
Running A
Running C
Running C

with two "stutters" for versions of run that B and D inherit without overriding (from A and C respectively). Assuming I'm right that this is not the effect you want, if you're keen to avoid super you could try changing run_all to:

def run_all(self):
    for cls in reversed(self.__class__.__mro__):
        meth = cls.__dict__.get('run')
        if meth is not None: meth(self)

which, substituted into my latest example with just two distinct defs for run in A and C, makes the example print:

Running A
Running C

which I suspect may be closer to what you want.

One more side point: don't repeat the work -- hasattr guarding getattr, or an in test guarding dict access -- both the check in the guard, and the guarded accessor, must repeat exactly the same work internally, to no good purpose. Rather, use a third argument of None to a single getattr call (or the get method of the dict): this means that if the method is absent you'll retrieve a None value, and then you can guard the call against that occurrence. This is exactly the reason dicts have a get method and getattr has a third optional "default" argument: to make it easy to apply DRY, "don't repeat yourself", a very important maxim of good programming!-)

于 2010-01-01T23:03:15.190 回答
1

你不应该使用__getattribute__方法..

只需执行以下操作:

getattr(cls, 'run')(self)
于 2010-01-01T22:29:15.813 回答
1

你为什么不简单地使用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()
于 2010-01-01T22:38:06.020 回答