Python 如何找到给定名称的类的所有子类?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3862310/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 13:06:12  来源:igfitidea点击:

How to find all the subclasses of a class given its name?

pythonsubclass

提问by Roman Prykhodchenko

I need a working approach of getting all classes that are inherited from a base class in Python.

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

采纳答案by unutbu

New-style classes (i.e. subclassed from object, which is the default in Python 3) have a __subclasses__method which returns the subclasses:

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

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

Here are the names of the subclasses:

以下是子类的名称:

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

Here are the subclasses themselves:

以下是子类本身:

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

Confirmation that the subclasses do indeed list Fooas their base:

确认子类确实Foo作为它们的基础列出:

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

Note if you want subsubclasses, you'll have to recurse:

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

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

Note that if the class definition of a subclass hasn't been executed yet - for example, if the subclass's module hasn't been imported yet - then that subclass doesn't exist yet, and __subclasses__won't find it.

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



You mentioned "given its name". Since Python classes are first-class objects, you don't need to use a string with the class's name in place of the class or anything like that. You can just use the class directly, and you probably should.

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

If you do have a string representing the name of a class and you want to find that class's subclasses, then there are two steps: find the class given its name, and then find the subclasses with __subclasses__as above.

如果您确实有一个表示类名称的字符串,并且您想查找该类的子类,则有两个步骤:根据名称查找类,然后使用__subclasses__上述方法查找子类。

How to find the class from the name depends on where you're expecting to find it. If you're expecting to find it in the same module as the code that's trying to locate the class, then

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

cls = globals()[name]

would do the job, or in the unlikely case that you're expecting to find it in locals,

会完成这项工作,或者在不太可能的情况下,您希望在当地人中找到它,

cls = locals()[name]

If the class could be in any module, then your name string should contain the fully-qualified name - something like 'pkg.module.Foo'instead of just 'Foo'. Use importlibto load the class's module, then retrieve the corresponding attribute:

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

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

However you find the class, cls.__subclasses__()would then return a list of its subclasses.

无论您如何找到该类,cls.__subclasses__()都会返回其子类的列表。

回答by martineau

This isn't as good an answer as using the special built-in__subclasses__()class method which @unutbu mentions, so I present it merely as an exercise. Thesubclasses()function defined returns a dictionary which maps all the subclass names to the subclasses themselves.

这不如使用__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)

Output:

输出:

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

回答by fletom

If you just want direct subclasses then .__subclasses__()works fine. If you want all subclasses, subclasses of subclasses, and so on, you'll need a function to do that for you.

如果您只想要直接子类,那么.__subclasses__()工作正常。如果你想要所有的子类、子类的子类等等,你需要一个函数来为你做这些。

Here's a simple, readable function that recursively finds all subclasses of a given class:

这是一个简单易读的函数,它递归地查找给定类的所有子类:

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

回答by martineau

Note: I see that someone (not @unutbu) changed the referenced answer so that it no longer uses vars()['Foo']— so the primary point of my post no longer applies.

注意:我看到有人(不是@unutbu)更改了引用的答案,使其不再使用vars()['Foo']——因此我的帖子的主要观点不再适用。

FWIW, here's what I meant about @unutbu's answeronly working with locally defined classes — and that using eval()instead of vars()would make it work with any accessible class, not only those defined in the current scope.

FWIW,这就是我对@unutbu 的回答仅适用于本地定义的类的意思——并且使用eval()而不是vars()将使其适用于任何可访问的类,而不仅仅是在当前范围内定义的类。

For those who dislike using eval(), a way is also shown to avoid it.

对于那些不喜欢使用 的人eval(),还显示了一种避免它的方法。

First here's a concrete example demonstrating the potential problem with using vars():

首先这是一个具体的例子,演示了使用的潜在问题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'>]

This could be improved by moving the eval('ClassName')down into the function defined, which makes using it easier without loss of the additional generality gained by using eval()which unlike vars()is not context-sensitive:

这可以通过将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'>]

Lastly, it's possible, and perhaps even important in some cases, to avoid using eval()for security reasons, so here's a version without it:

最后,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'>]

回答by Kimvais

The simplest solution in general form:

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

def get_subclasses(cls):
    for subclass in cls.__subclasses__():
        yield from get_subclasses(subclass)
        yield subclass

And a classmethod in case you have a single class where you inherit from:

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

@classmethod
def get_subclasses(cls):
    for subclass in cls.__subclasses__():
        yield from subclass.get_subclasses()
        yield subclass

回答by Peter Brooks

A much shorter version for getting a list of all subclasses:

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

from itertools import chain

def subclasses(cls):
    return list(
        chain.from_iterable(
            [list(chain.from_iterable([[x], subclasses(x)])) for x in cls.__subclasses__()]
        )
    )

回答by Or Duan

Python 3.6- __init_subclass__

Python 3.6-__init_subclass__

As other answer mentioned you can check the __subclasses__attribute to get the list of subclasses, since python 3.6 you can modify this attribute creation by overriding the __init_subclass__method.

正如其他答案所提到的,您可以检查__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

This way, if you know what you're doing, you can override the behavior of of __subclasses__and omit/add subclasses from this list.

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

回答by Thomas Grainger

Here's a version without recursion:

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

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

This differs from other implementations in that it returns the original class. This is because it makes the code simpler and:

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

class Ham(object):
    pass

assert(issubclass(Ham, Ham)) # True

If get_subclasses_gen looks a bit weird that's because it was created by converting a tail-recursive implementation into a looping generator:

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

回答by Aaron Hall

How can I find all subclasses of a class given its name?

如何找到给定名称的类的所有子类?

We can certainly easily do this given access to the object itself, yes.

如果可以访问对象本身,我们当然可以轻松做到这一点,是的。

Simply given its name is a poor idea, as there can be multiple classes of the same name, even defined in the same module.

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

I created an implementation for another answer, and since it answers this question and it's a little more elegant than the other solutions here, here it is:

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

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

Usage:

用法:

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