tags:

views:

157

answers:

3

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.

+8  A: 

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.

kigurai
oh.. that's nice, I didn't know that. :)
Stefano Borini
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
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
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
+1  A: 

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.

quamrana
+1  A: 

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)
ephesian
There are indentation errors in the for-loop
kigurai
@kigurai, thanks for spotting that ! The problem was a tab masquerading as 4 spaces. I've fixed it now
ephesian