Как узнать все производные классы родителя?

Предположим, что у вас есть базовый класс A, и этот класс переопределяется B и C. Предположим также, что существует метод класса A.derived(), который говорит вам, какие классы переопределяют A, поэтому возвращает [B, C], и если вы позже имеете class D(A): pass или class D(B): pass, теперь A.derived() возвращает [B, C, D].

Как бы вы реализовали метод A.derived()? У меня такое ощущение, что это невозможно, если вы не используете метаклассы. Вы можете пересечь дерево наследования только с дочернего на родительский с помощью стандартного механизма. Чтобы иметь ссылку в другом направлении, вы должны держать ее "вручную", а это означает отмену традиционной механики декларации классов.

Ответы

Ответ 1

Если вы определяете свои классы как класс нового стиля (подкласс object), это возможно, поскольку подклассы сохраняются в __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

Я не знаю точно, когда это было введено, или если есть какие-то особые соображения. Однако он отлично работает, чтобы объявить подкласс в функции:

>>> 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

Однако вам нужно отметить, что до тех пор, пока не будут выполнены определения классов, интерпретатор не знает о них. В приведенном выше примере C не распознается как подкласс A до тех пор, пока не будет выполнена функция f. Но в любом случае это одинаково для классов python, поскольку я полагаю, что вы уже знаете.

Ответ 2

Учитывая обсуждение подклассов, реализация может выглядеть так:

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

edit: вы также можете посмотреть на этот ответ на несколько другой вопрос.

Ответ 3

Вот еще одна реализация, которая будет рекурсивно распечатывать все подклассы с отступом.

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)