23

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.

Stefano Borini
  • 138,652
  • 96
  • 297
  • 431

3 Answers3

23

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.

Hannes Ovrén
  • 21,229
  • 9
  • 65
  • 75
  • oh.. that's nice, I didn't know that. :) – Stefano Borini Feb 08 '10 at 07:33
  • 2
    The `__subclasses__` method is an undocumented implementation detail, so may be removed in future versions of Python. It is also not implemented in other versions of Python such as Jython and IronPython. Use with care. – Dave Kirby Feb 08 '10 at 07:37
  • 6
    Scratch what I said in my previous comment - I just checked and it has been added to the docs for 3.x, and is implemented in Jython 2.5 so I guess it is now official and here to stay. I don't have IronPython installed, so can't comment on on whether it is implemented in that yet. – Dave Kirby Feb 08 '10 at 07:43
  • 4
    Ok, additional note for future reference: `__subclasses__` does __not__ walk through the whole tree, only the direct reimplementors. It is relatively easy, however, to define a method `A.derived()` so to traverse the whole tree. – Stefano Borini Feb 08 '10 at 07:47
3

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.

Community
  • 1
  • 1
quamrana
  • 37,849
  • 12
  • 53
  • 71
2

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)
Philip Clarke
  • 727
  • 6
  • 13