如何根据名称查找类的所有子类?

Rom*_*nko 194 python subclass

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

unu*_*tbu 267

新类(即子类object,Python 3中的默认类)有一个__subclasses__返回子类的方法:

class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass
Run Code Online (Sandbox Code Playgroud)

以下是子类的名称:

print([cls.__name__ for cls in Foo.__subclasses__()])
# ['Bar', 'Baz']
Run Code Online (Sandbox Code Playgroud)

以下是子类本身:

print(Foo.__subclasses__())
# [<class '__main__.Bar'>, <class '__main__.Baz'>]
Run Code Online (Sandbox Code Playgroud)

确认子类确实Foo列为其基数:

for cls in Foo.__subclasses__():
    print(cls.__base__)
# <class '__main__.Foo'>
# <class '__main__.Foo'>
Run Code Online (Sandbox Code Playgroud)

请注意,如果您想要子类,则必须递归:

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'>}
Run Code Online (Sandbox Code Playgroud)

请注意,如果尚未执行子类的类定义 - 例如,如果尚未导入子类的模块 - 那么该子类尚不存在,并且__subclasses__不会找到它.


你提到"给它的名字".由于Python类是第一类对象,因此您不需要使用具有类名称的字符串来代替类或类似的东西.您可以直接使用该类,您可能应该这样做.

如果您确实有一个表示类名称的字符串,并且您想要查找该类的子类,那么有两个步骤:找到给定其名称的类,然后__subclasses__按上述方法查找子类.

如何从名称中找到该类取决于您期望找到它的位置.如果您希望在与尝试定位类的代码相同的模块中找到它,那么

cls = globals()[name]
Run Code Online (Sandbox Code Playgroud)

会做这个工作,或者在你希望在当地人找到它的不太可能的情况下,

cls = locals()[name]
Run Code Online (Sandbox Code Playgroud)

如果类可以在任何模块中,那么您的名称字符串应该包含完全限定的名称 - 'pkg.module.Foo'而不仅仅是'Foo'.使用importlib加载类的模块,然后获取相应的属性:

import importlib
modname, _, clsname = name.rpartition('.')
mod = importlib.import_module(modname)
cls = getattr(mod, clsname)
Run Code Online (Sandbox Code Playgroud)

但是,如果找到该类,cls.__subclasses__()则会返回其子类列表.

  • 假设我想查找模块中的所有子类,无论包含该模块的子模块是否已导入? (4认同)
  • @SamanthaAtkins:生成[包的所有子模块的列表](/sf/ask/119539661/),然后生成[每个模块的所有类的列表](https://stackoverflow. com/q/1796180/190597)。 (3认同)

fle*_*tom 61

如果你只是想要直接子类,那么.__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
Run Code Online (Sandbox Code Playgroud)

  • 谢谢@fletom!虽然我需要回到那些日子只是__subclasses __()你的解决方案非常好.带你+1;)顺便说一句,我认为在你的情况下使用发电机可能更可靠. (3认同)
  • `all_subclasses`不应该是'set`来消除重复吗? (2认同)

Kim*_*ais 28

一般形式的最简单的解决方案:

def get_subclasses(cls):
    for subclass in cls.__subclasses__():
        yield from get_subclasses(subclass)
        yield subclass
Run Code Online (Sandbox Code Playgroud)

还有一个类方法,以防你有一个继承自的类:

@classmethod
def get_subclasses(cls):
    for subclass in cls.__subclasses__():
        yield from subclass.get_subclasses()
        yield subclass
Run Code Online (Sandbox Code Playgroud)

  • 生成器方法非常干净。 (3认同)

Or *_*uan 17

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
Run Code Online (Sandbox Code Playgroud)

这样,如果您知道自己在做什么,就可以覆盖此行为,__subclasses__并从此列表中省略/添加子类.


mar*_*eau 9

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'>]
Run Code Online (Sandbox Code Playgroud)

这可以通过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'>]
Run Code Online (Sandbox Code Playgroud)

最后,在某些情况下,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'>]
Run Code Online (Sandbox Code Playgroud)


dhn*_*nam 9

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

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)
Run Code Online (Sandbox Code Playgroud)

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