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

时间:2010-10-05 09:17:35

标签: python subclass

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

11 个答案:

答案 0 :(得分:231)

新式类(即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__()将返回其子类列表。

答案 1 :(得分:57)

如果您只想要直接子类,那么.__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 :(得分:25)

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

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 :(得分:14)

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 :(得分:7)

FWIW,这里我的意思是@unutbu's answer仅使用本地定义的类 - 并且使用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'>] ,但在某些情况下甚至可能更重要,所以这里是没有它的版本:

{{1}}

答案 5 :(得分:3)

用于获取所有子类列表的更短版本:

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 :(得分:1)

这不像使用@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'>}

答案 7 :(得分:1)

这是一个没有递归的版本:

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], [])

答案 8 :(得分:1)

  

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

我们可以很容易地做到这一点,因为访问对象本身,是的。

简单地给出它的名字是一个糟糕的主意,因为可以有多个同名的类,甚至在同一个模块中定义。

我为另一个answer创建了一个实现,因为它回答了这个问题,并且它比其他解决方案更优雅,这里是:

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

答案 9 :(得分:1)

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

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^2)时有偏见的树。

答案 10 :(得分:0)

我无法想象它的真实世界用例,但是一种强大的方式(即使在Python 2旧样式类中)也将扫描全局命名空间:

def has_children(cls):
    g = globals().copy()   # use a copy to make sure it will not change during iteration
    g.update(locals())     # add local symbols
    for k, v in g.items(): # iterate over all globals object
        try:
            if (v is not cls) and issubclass(v, cls): # found a strict sub class?
                return True
        except TypeError:  # issubclass raises a TypeError if arg is not a class...
            pass
    return False

适用于Python 2新样式类和Python 3类以及Python 2 经典