tags:

views:

594

answers:

5

In Python, given a module X and a class Y, how can I iterate or generate a list of all subclasses of Y that exist in module X?

+2  A: 

Here's one way to do it:

import inspect

def get_subclasses(mod, cls):
    """Yield the classes in module ``mod`` that inherit from ``cls``"""
    for name, obj in inspect.getmembers(mod):
        if hasattr(obj, "__bases__") and cls in obj.__bases__:
            yield obj
Chris AtLee
My solution won't return classes that are not direct descendants of 'cls'. quamrana's solution below will find any class that has 'cls' somewhere in its ancestry.
Chris AtLee
+1  A: 

Given the module foo.py

class foo(object): pass
class bar(foo): pass
class baz(foo): pass

class grar(Exception): pass

def find_subclasses(module, clazz):
    for name in dir(module):
        o = getattr(module, name)

        try: 
             if issubclass(o, clazz):
             yield name, o
        except TypeError: pass

>>> import foo
>>> list(foo.find_subclasses(foo, foo.foo))
[('bar', <class 'foo.bar'>), ('baz', <class 'foo.baz'>), ('foo', <class 'foo.foo'>)]
>>> list(foo.find_subclasses(foo, object))
[('bar', <class 'foo.bar'>), ('baz', <class 'foo.baz'>), ('foo', <class 'foo.foo'>), ('grar', <class 'foo.grar'>)]
>>> list(foo.find_subclasses(foo, Exception))
[('grar', <class 'foo.grar'>)]
Aaron Maenpaa
+4  A: 

Can I suggest that neither of the answers from Chris AtLee and zacherates fulfill the requirements? I think this modification to zacerates answer is better:

def find_subclasses(module, clazz):
    for name in dir(module):
     o = getattr(module, name)
     try:
      if (o != clazz) and issubclass(o, clazz):
       yield name, o
     except TypeError: pass

The reason I disagree with the given answers is that the first does not produce classes that are a distant subclass of the given class, and the second includes the given class.

quamrana
A: 

quamrana, I hadn't noticed that. Unfortunately I posted the question without registering so I can't select an alternate answer.

-Mark

+8  A: 

Although Quamrana's suggestion works fine, there are a couple of possible improvements I'd like to suggest to make it more pythonic. They rely on using the inspect module from the standard library.

  1. You can avoid the getattr call by using inspect.getmembers()
  2. The try/catch can be avoided by using inspect.isclass()

With those, you can reduce the whole thing to a single list comprehension if you like:

def find_subclasses(module, clazz):
    return [ cls for cls in inspect.getmembers(module) if inspect.isclass(cls) and
                                                                                issubclass(cls, clazz) ]
runeh
inspect.getmembers returns a tuple, so you wantcls for name, cls in inspect.getmembers(module)
jelovirt
Works great, but my response also returns the base class (the one I send in with clazz), any idea?
fredrik
Fredrik, turns out issubclass(Foo, Foo) is True. Easy fix though. add "and not cls is clazz" to the list comprehension
runeh