How to know all the derived classes of a parent?

Viewed 5156

Suppose you have a base class A, and this class is reimplemented by B and C. Suppose also there's a class method A.derived() that tells you which classes are reimplementing A, hence returns [B, C], and if you later on have class D(A): pass or class D(B): pass, now A.derived() returns [B,C,D].

How would you implement the method A.derived() ? I have the feeling that is not possible unless you use metaclasses. You can traverse the inheritance tree only from child to parent with the standard mechanism. To have the link in the other direction, you have to keep it "by hand", and this means overriding the traditional class declaration mechanics.

3 Answers

If you define your classes as a new-style class (subclass of object) then this is possible since the subclasses are saved in __subclasses__.

class A(object):
 def hello(self):
  print "Hello A"

class B(A):
 def hello(self):
   print "Hello B"

>>> for cls in A.__subclasses__():
...  print cls.__name__
...
B

I do not know exactly when this was introduced or if there are any special considerations. It does however work fine to declare a subclass in a function:

>>> def f(x):
...  class C(A):
...   def hello(self):
...    print "Hello C"
...  c = C()
...  c.hello()
...  print x
...  for cls in A.__subclasses__():
...   print cls.__name__
...
>>> f(4)
Hello C
4
B
C

However, you need to note that until the class definitions have been run the interpreter does not know about them. In the example above C is not recognized as a subclass of A until the function f is executed. But this is the same for python classes every time anyway, as I presume you are already aware of.

Given the discussion on subclasses then an implementation might look like this:

class A(object):
    @classmethod
    def derived(cls):
        return [c.__name__ for c in cls.__subclasses__()]

edit: you might also want to look at this answer to a slightly different question.

Here is another implementation that will recursively print out all subclasses, with indentation.

def findsubclass(baseclass, indent=0):
    if indent == 0:
        print "Subclasses of %s are:" % baseclass.__name__
    indent = indent + 1
    for c in baseclass.__subclasses__():
        print "-"*indent*4 + ">" + c.__name__
        findsubclass(c, indent)
Related