问题:如何找到给定名称的类的所有子类?
我需要一种工作方法来获取所有从Python基类继承的类。
回答 0
新型类(即objectPython中的默认子类来自)具有一种__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'而不是just 'Foo'。使用importlib加载类的模块,然后获取相应的属性:
import importlib
modname, _, clsname = name.rpartition('.')
mod = importlib.import_module(modname)
cls = getattr(mod, clsname)但是,找到该类后,cls.__subclasses__()将返回其子类的列表。
回答 1
如果您只想要直接子类,那么.__subclasses__()效果很好。如果需要所有子类,子类的子类等等,则需要一个函数来为您执行此操作。
这是一个简单易读的函数,它递归地找到给定类的所有子类:
def get_all_subclasses(cls):
    all_subclasses = []
    for subclass in cls.__subclasses__():
        all_subclasses.append(subclass)
        all_subclasses.extend(get_all_subclasses(subclass))
    return all_subclasses回答 2
最简单的一般形式的解决方案:
def get_subclasses(cls):
    for subclass in cls.__subclasses__():
        yield from get_subclasses(subclass)
        yield subclass还有一个类方法,以防您有一个继承自的类:
@classmethod
def get_subclasses(cls):
    for subclass in cls.__subclasses__():
        yield from subclass.get_subclasses()
        yield subclass回答 3
Python的3.6 –__init_subclass__
正如提到的其他答案一样,您可以检查__subclasses__属性以获取子类列表,因为python 3.6可以通过覆盖__init_subclass__方法。
class PluginBase:
    subclasses = []
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)
class Plugin1(PluginBase):
    pass
class Plugin2(PluginBase):
    pass这样,如果您知道自己在做什么,则可以覆盖__subclasses__此列表的行为并忽略/添加子类。
回答 4
注意:我看到有人(不是@unutbu)更改了引用的答案,以使其不再使用vars()['Foo']-因此,我帖子的重点不再适用。
FWIW,这就是我对@unutbu的答案仅适用于本地定义的类的意思-使用eval()代替代替vars()将使其适用于任何可访问的类,而不仅限于当前范围内定义的那些类。
对于那些不喜欢使用的人eval(),还显示了一种避免使用它的方法。
首先,这是一个具体示例,演示使用的潜在问题vars():
class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass
# unutbu's approach
def all_subclasses(cls):
    return cls.__subclasses__() + [g for s in cls.__subclasses__()
                                       for g in all_subclasses(s)]
print(all_subclasses(vars()['Foo']))  # Fine because  Foo is in scope
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]
def func():  # won't work because Foo class is not locally defined
    print(all_subclasses(vars()['Foo']))
try:
    func()  # not OK because Foo is not local to func()
except Exception as e:
    print('calling func() raised exception: {!r}'.format(e))
    # -> calling func() raised exception: KeyError('Foo',)
print(all_subclasses(eval('Foo')))  # OK
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]
# using eval('xxx') instead of vars()['xxx']
def func2():
    print(all_subclasses(eval('Foo')))
func2()  # Works
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]可以通过eval('ClassName')向下移动到定义的函数中来改进此功能,这使使用起来更容易,同时又不损失使用eval()不与vars()上下文无关的不与之相关的其他普遍性:
# easier to use version
def all_subclasses2(classname):
    direct_subclasses = eval(classname).__subclasses__()
    return direct_subclasses + [g for s in direct_subclasses
                                    for g in all_subclasses2(s.__name__)]
# pass 'xxx' instead of eval('xxx')
def func_ez():
    print(all_subclasses2('Foo'))  # simpler
func_ez()
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]最后,eval()出于安全原因,有可能避免使用,甚至在某些情况下甚至很重要,因此下面是一个没有它的版本:
def get_all_subclasses(cls):
    """ Generator of all a class's subclasses. """
    try:
        for subclass in cls.__subclasses__():
            yield subclass
            for subclass in get_all_subclasses(subclass):
                yield subclass
    except TypeError:
        return
def all_subclasses3(classname):
    for cls in get_all_subclasses(object):  # object is base of all new-style classes.
        if cls.__name__.split('.')[-1] == classname:
            break
    else:
        raise ValueError('class %s not found' % classname)
    direct_subclasses = cls.__subclasses__()
    return direct_subclasses + [g for s in direct_subclasses
                                    for g in all_subclasses3(s.__name__)]
# no eval('xxx')
def func3():
    print(all_subclasses3('Foo'))
func3()  # Also works
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]回答 5
一个简短的版本,用于获取所有子类的列表:
from itertools import chain
def subclasses(cls):
    return list(
        chain.from_iterable(
            [list(chain.from_iterable([[x], subclasses(x)])) for x in cls.__subclasses__()]
        )
    )回答 6
如何找到给定名称的类的所有子类?
如果可以访问对象本身,我们当然可以轻松地做到这一点,是的。
仅仅给出其名称是一个糟糕的主意,因为甚至在同一个模块中定义了多个具有相同名称的类。
我为另一个答案创建了一个实现,由于它可以回答这个问题,并且比此处的其他解决方案要优雅一些,这里是:
def get_subclasses(cls):
    """returns all subclasses of argument, cls"""
    if issubclass(cls, type):
        subclasses = cls.__subclasses__(cls)
    else:
        subclasses = cls.__subclasses__()
    for subclass in subclasses:
        subclasses.extend(get_subclasses(subclass))
    return subclasses用法:
>>> import pprint
>>> list_of_classes = get_subclasses(int)
>>> pprint.pprint(list_of_classes)
[<class 'bool'>,
 <enum 'IntEnum'>,
 <enum 'IntFlag'>,
 <class 'sre_constants._NamedIntConstant'>,
 <class 'subprocess.Handle'>,
 <enum '_ParameterKind'>,
 <enum 'Signals'>,
 <enum 'Handlers'>,
 <enum 'RegexFlag'>]回答 7
这不是使用__subclasses__()@unutbu提到的特殊内置类方法的好答案,因此我仅作为练习来介绍它。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'>}回答 8
这是一个没有递归的版本:
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], [])
