If you know you're always dealing with new-style classes:
def numberofancestors(klass):
return len(klass.mro())
or, if you worry there may be old-style classes in the mix:
import inspect
def numberofancestors(klass):
return len(inspect.getmro(klass))
and then, in either case,
sorted(HANDLERS, key=numberofancestors, reversed=True)
will give you what you require (you don't need the .keys()
part).
@Ignacio's suggestion of a topological sort is theoretically correct, but since, given a class, you can easily and rapidly get the number of its precursors (AKA "ancestors"... in a weird sense of the word whereby you're one of your ancestors;-), with these numberofancestors
functions, my approach is much more practical: it relies on the obvious fact that any derived class has at least one more "ancestor" than any of its bases classes, and therefore, with this key=
, it will always sort before any of its bases.
Unrelated classes may end up in arbitrary order (just like they might in a topological sort), but you've made it clear you don't care about this.
Edit: the OP, musing in the following comments thread about optimal support for multiple inheritance cases, came up with a drastically different idea than the original one of "pre-sorting" embedded in the question, but his suggestion on how to implement that drastically idea is not optimal:
[h for h in [HANDLERS.get(c) for c in type(obj).mro()] if h is not None][0]
the idea is good (if multiple-inheritance support is of any interest) but the best implementation thereof would probably be (Python 2.6 or better):
next(Handlers[c] for c in type(obj).mro() if c in Handlers)
Normally, adict.get(k) and check for not None
is faster than if k in adict: adict[k]
, but this is not a particularly normal case because to use get
requires building a "fake" one-item list and "looping" on it to simulate assignment.
More generally, building a whole list via comprehension just to take its [0]
th item is excess work -- the next
builtin function called on a genexp acts more like a first
, as in, "give me the first item of the genexp" and does no extra work beyond that. It raises StopIteration instead of IndexError if the listcomp/genexp is empty, but that's not normally an issue; you could also have a second argument to next
, to be uses as the "default value" if the genexp is empty.
In 2.5 and earlier you'd have to use (thegenexp).next()
instead (and there's no way to give it a default argument), but while syntactically a tad less shiny it's more or less equivalent to the 2.6-and-better construct in semantics and speed.
I'm sure glad the discussion continued in the comments because I think this resulting conclusion is worthwhile and potentially useful (though maybe not in the exact environment of the OP's application, where multiple inheritance may not in fact be an issue).