views:

234

answers:

3

I have an object of class 'D' in python, and I want to sequentially execute the 'run' method as defined by 'D' and each of it's ancestors ('A', 'B' and 'C').

I'm able to accomplish this like this

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

Which results in

$ python test.py 
Running A
Running B
Running C
Running D

However in practice I won't know the name of the method to be executed. But if I try this using getattribute() (see the commented) line it doesn't work:

$ python test.py 
Running D
Running D
Running D
Running D

So my questions are:

  1. Why isn't it working?

  2. Is this even the best way to go about this?

+2  A: 

You should not use __getattribute__ method..

just do the following:

getattr(cls, 'run')(self)
Pydev UA
you can use __getattribute__, but it will look very "not nice" - cls.__getattribute__(cls, 'run')(self)
Pydev UA
+1  A: 

Why don't you simply use super? Although some consider it harmful, it was designed with exactly this kind of scenario in mind, and I would use it without any hesitation.

From Python documentation:

This is useful for accessing inherited methods that have been overridden in a class. The search order is same as that used by getattr() except that the type itself is skipped. [...] This makes it possible to implement “diamond diagrams” where multiple base classes implement the same method.

Update: In your case, it would become something like this:

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()
Roberto Liffredo
I tried that originally, but I had trouble with super(self.__class__, self) (it always seems to return self.__class__, at least in the situation above) and I don't want the user to have to call it every time.
russell_h
Yep, `super` requires explicit specification of the class, as in my answer's first code suggestion -- using `self.__class__` in it just can't work right. (Python 3 is a bit better that way, btw).
Alex Martelli
Why don't you use directly super(D, self).run() ? I am putting an example in the main answer.
Roberto Liffredo
In my case subclasses will represent server configurations which can be inherited, etc, and I'm trying to keep the syntax as declarative as possible within the limits of python. So I don't want every method of every subclass to have to manually call super.
russell_h
@Roberto, you're simply wrong in stating that "you need to call super even on the "root" class of your hierarchy"! Just **try** your code, and you'll see a traceback with `AttributeError: 'super' object has no attribute 'run'`; remove that `super` call in `A.run` which you claim is needed, just like my answer's code omitted it, and you'll see the traceback disappear, and everything just work... exactly as it works in my answer, of course, since, once this terrible error is fixed, your code's then identical to mine;-).
Alex Martelli
@russel: I _suspect_ you are somewhat reimplementing a language feature, just to hide some mechanism; however, there's a good chance that this is one of the cases where "explicit is better than implicit" and I would suggest at least trying a solution in that way.
Roberto Liffredo
@Alex: you are _definitely_ right. I was confusing with __init__, that is defined in object. Thanks for the tip!
Roberto Liffredo
+3  A: 

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

Alex Martelli
This is exactly what I was looking for, and you are quite right about the "stutters", I hadn't thought of that. Thanks everyone!
russell_h
Also, as I understand it, to use super with multiple inheritance you do need to call it from the 'root' class in order to traverse the whole tree/diamond/whatever (as Roberto did below), although I could be wrong on that.
russell_h
@russel_h, no, if there's a common root class that first introduces a method it can (and often should, like here) avoid using `super` in it: that's why my first example works fine (but would break if `A.run` used `super`, unless within a suitable `try`/`except` `try` clause). Please try my code, and @Roberto's (see also my latest comment to his answer), and you'll see that I'm right and he's wrong: it's not really a particularly _debatable_ issue!-)
Alex Martelli
Ah, I see what you mean. I stand corrected.
russell_h