53

Is there any way in python to query a namespace for classes which inherit from a particular class? Given a class widget I'd like to be able to call something like inheritors(widget) to get a list of all my different kinds of widget.

kdt
  • 27,905
  • 33
  • 92
  • 139

4 Answers4

80

You want to use Widget.__subclasses__() to get a list of all the subclasses. It only looks for direct subclasses though so if you want all of them you'll have to do a bit more work:

def inheritors(klass):
    subclasses = set()
    work = [klass]
    while work:
        parent = work.pop()
        for child in parent.__subclasses__():
            if child not in subclasses:
                subclasses.add(child)
                work.append(child)
    return subclasses

N.B. If you are using Python 2.x this only works for new-style classes.

Duncan
  • 92,073
  • 11
  • 122
  • 156
  • 1
    So in other words, if you are on Python 2.x, have your super class inherit from `object`: `class SomeClass(object):`. – RattleyCooper Apr 11 '16 at 20:53
  • This `__subclasses__()` approach only works if the parent class and subclasses are in the same module. If you define your subclasses in some other module then you won't find them – Kashif Siddiqui Mar 02 '17 at 00:33
  • 3
    @KashifSiddiqui that's not true. `__subclasses__` includes all subclasses no matter where they're declared. However it won't see them until after the relevant subclasses have been defined, so if you just extract the list of subclasses at the end of the module that defined the base class the subclasses in other modules won't yet exist.. – Duncan Mar 02 '17 at 09:13
  • @Duncan yeah you are right that the base class will detect any subclass as soon as we import that subclass (Don't even need to instantiate it). But doesn't that defeat the purpose of "detecting" a subclass. The use case I was referring to is when we create a base let's say `Handler` or something and then define `SubHandlers` somewhere else. Then in order to detect those handlers the app would need to import all subclasses in order for the base `Handler` to detect all `__subclasses__` – Kashif Siddiqui Mar 03 '17 at 00:26
  • 2
    @KashifSiddiqui, yes because until you import them they aren't subclasses, they're just some text in a file. So you import a bunch of files and then need to know all the subclasses they defined. It's quite a common scenario. – Duncan Mar 03 '17 at 09:12
  • You don't need the `if child not in subclasses` since set addition is idempotent, and inheritance prevents cycles. – Josh Albert Apr 30 '22 at 18:41
23

You can track inheritance with your own metaclass

import collections

class A(object):
    class __metaclass__(type):
        __inheritors__ = defaultdict(list)

        def __new__(meta, name, bases, dct):
            klass = type.__new__(meta, name, bases, dct)
            for base in klass.mro()[1:-1]:
                meta.__inheritors__[base].append(klass)
            return klass

class B(A):
    pass

class C(B):
    pass

>>> A.__inheritors__
defaultdict(<type 'list'>, {<class '__main__.A'>: [<class '__main__.B'>, <class '__main__.C'>], <class '__main__.B'>: [<class '__main__.C'>]})

Anything that is inherited from A or it's derived classes will be tracked. You will get full inheritance map when all the modules in your application are loaded.

Imran
  • 87,203
  • 23
  • 98
  • 131
  • 3
    The subclass relationship is already tracked for you by Python's `__subclass__()` method. – Duncan May 04 '11 at 12:25
  • 2
    Thanks for pointing it out, totally forgot about this. My solution also tracks the indirect subclasses, the extra work you talked about in your answer. – Imran May 04 '11 at 12:43
1
# return list of tuples (objclass, name) containing all subclasses in callers' module
def FindAllSubclasses(classType):
    import sys, inspect
    subclasses = []
    callers_module = sys._getframe(1).f_globals['__name__']
    classes = inspect.getmembers(sys.modules[callers_module], inspect.isclass)
    for name, obj in classes:
        if (obj is not classType) and (classType in inspect.getmro(obj)):
            subclasses.append((obj, name))
    return subclasses
formiaczek
  • 385
  • 3
  • 7
0

You have to walk through all objects in the global namespace (globals()) and check if the related object/class is a subclass of the some other class (check the Python docs for issubclass()).