我需要一种工作方法来获取从Python基类继承的所有类。


当前回答

虽然我非常倾向于__init_subclass__方法,这将保留定义顺序,并避免组合增长顺序,如果你有一个非常密集的层次结构,到处都有多个继承:

def descendents(cls):
    '''Does not return the class itself'''
    R = {}
    def visit(cls):
        for subCls in cls.__subclasses__():
            R[subCls] = True
            visit(subCls)
    visit(cls)
    return list(R.keys())

这是因为字典会记住键的插入顺序。列表方法也会起作用。

其他回答

新风格的类(即从object继承的子类,这是Python 3中的默认值)有__subclasses__方法,该方法返回子类:

class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass

下面是子类的名称:

print([cls.__name__ for cls in Foo.__subclasses__()])
# ['Bar', 'Baz']

下面是子类本身:

print(Foo.__subclasses__())
# [<class '__main__.Bar'>, <class '__main__.Baz'>]

确认子类确实将Foo列为基类:

for cls in Foo.__subclasses__():
    print(cls.__base__)
# <class '__main__.Foo'>
# <class '__main__.Foo'>

注意,如果你想要子类,你必须递归:

def all_subclasses(cls):
    return set(cls.__subclasses__()).union(
        [s for c in cls.__subclasses__() for s in all_subclasses(c)])

print(all_subclasses(Foo))
# {<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>}

注意,如果一个子类的类定义还没有被执行——例如,如果子类的模块还没有被导入——那么这个子类还不存在,__subclasses__将找不到它。


你提到了“以其名字命名”。由于Python类是一级对象,所以不需要使用带有类名的字符串来代替类或类似的东西。您可以直接使用该类,而且您可能应该这样做。

如果你确实有一个表示类名的字符串,并且你想要找到该类的子类,那么有两个步骤:找到给定其名称的类,然后像上面那样找到带有__subclasses__的子类。

如何从名称中找到类取决于您希望在哪里找到它。如果您希望在与试图定位类的代码相同的模块中找到它,那么

cls = globals()[name]

会起作用,或者在不太可能的情况下,你期望在当地人身上找到它,

cls = locals()[name]

如果这个类可以在任何模块中,那么你的名称字符串应该包含完全限定的名称——比如'pkg.module '。Foo'而不是Foo'。使用importlib加载类的模块,然后检索相应的属性:

import importlib
modname, _, clsname = name.rpartition('.')
mod = importlib.import_module(modname)
cls = getattr(mod, clsname)

无论你如何找到这个类,cls.__subclasses__()将返回它的子类列表。

下面是一个简单但有效的代码版本:

def get_all_subclasses(cls):
    subclass_list = []

    def recurse(klass):
        for subclass in klass.__subclasses__():
            subclass_list.append(subclass)
            recurse(subclass)

    recurse(cls)

    return set(subclass_list)

它的时间复杂度是O(n)如果没有多重继承,n是所有子类的数目。 它比递归地创建列表或使用生成器生成类的函数更有效,后者的复杂度可能是(1)O(nlogn)当类层次结构是平衡树时,或(2)O(n²)当类层次结构是有偏树时。

虽然我非常倾向于__init_subclass__方法,这将保留定义顺序,并避免组合增长顺序,如果你有一个非常密集的层次结构,到处都有多个继承:

def descendents(cls):
    '''Does not return the class itself'''
    R = {}
    def visit(cls):
        for subCls in cls.__subclasses__():
            R[subCls] = True
            visit(subCls)
    visit(cls)
    return list(R.keys())

这是因为字典会记住键的插入顺序。列表方法也会起作用。

下面是一个没有递归的版本:

def get_subclasses_gen(cls):

    def _subclasses(classes, seen):
        while True:
            subclasses = sum((x.__subclasses__() for x in classes), [])
            yield from classes
            yield from seen
            found = []
            if not subclasses:
                return

            classes = subclasses
            seen = found

    return _subclasses([cls], [])

这与其他实现的不同之处在于它返回原始类。 这是因为它使代码更简单,并且:

class Ham(object):
    pass

assert(issubclass(Ham, Ham)) # True

如果get_subclasses_gen看起来有点奇怪,那是因为它是通过将尾递归实现转换为循环生成器创建的:

def get_subclasses(cls):

    def _subclasses(classes, seen):
        subclasses = sum(*(frozenset(x.__subclasses__()) for x in classes))
        found = classes + seen
        if not subclasses:
            return found

        return _subclasses(subclasses, found)

    return _subclasses([cls], [])

这个答案不如使用@unutbu提到的特殊内置__subclasses__()类方法好,所以我只是把它作为一个练习。subclasses()函数的定义返回一个字典,该字典将所有子类名称映射到子类本身。

def traced_subclass(baseclass):
    class _SubclassTracer(type):
        def __new__(cls, classname, bases, classdict):
            obj = type(classname, bases, classdict)
            if baseclass in bases: # sanity check
                attrname = '_%s__derived' % baseclass.__name__
                derived = getattr(baseclass, attrname, {})
                derived.update( {classname:obj} )
                setattr(baseclass, attrname, derived)
             return obj
    return _SubclassTracer

def subclasses(baseclass):
    attrname = '_%s__derived' % baseclass.__name__
    return getattr(baseclass, attrname, None)


class BaseClass(object):
    pass

class SubclassA(BaseClass):
    __metaclass__ = traced_subclass(BaseClass)

class SubclassB(BaseClass):
    __metaclass__ = traced_subclass(BaseClass)

print subclasses(BaseClass)

输出:

{'SubclassB': <class '__main__.SubclassB'>,
 'SubclassA': <class '__main__.SubclassA'>}