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