关于python:如何根据名称查找类的所有子类?

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

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


新风格(I.E.subclassed from EDOCX1&0),which is the default in Python 3)have a EDOCX1&1)method which returns the subclasses:

1
2
3
4
class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass

There are the names of the subclasses:

ZZU1

这里是Themselves的亚类:

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

2.确认次类为Foo的指数列表:

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

如果你想要补助金,你必须退还:

1
2
3
4
5
6
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__找到亚班。

如何从名称中找到这一类取决于你打算在哪找到它。如果你指望在同一个模块中找到它作为代码

1
cls = globals()[name]

你可以做这份工作,或者在你指望在当地找到的不寻常的情况下,

1
cls = locals()[name]

如果班级可以在任何模块中,那么你的名字应该包含完全合格的名字使用importlib载荷舱模块,然后检索相应的属性:

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

你怎么找到班级,cls.__subclasses__()


如果你只想直接上亚班,那么就做得好。如果你想要所有的亚类,亚类,那么你需要一个功能来为你。

这是一个简单的、可实现的功能,它可归纳出一个吉普赛人的所有亚类:

1
2
3
4
5
6
7
8
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


The simplest solution in general form:

1
2
3
4
def get_subclasses(cls):
    for subclass in cls.__subclasses__():
        yield from get_subclasses(subclass)
        yield subclass

一种分类方法,以你有一个单一的类别,你来自:

1
2
3
4
5
@classmethod
def get_subclasses(cls):
    for subclass in cls.__subclasses__():
        yield from subclass.get_subclasses()
        yield subclass


Python3.6

作为另一个答案,你可以检查__subclasses__〔1〕自从Python3.6你可以通过EDOCX1〕〔20〕来修改这个属性的产生。

1
2
3
4
5
6
7
8
9
10
11
12
class PluginBase:
    subclasses = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)

class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass

如果你知道自己在做什么,你可以超越EDOCX1的行为和OMIT/ADD的亚类。


FWIW,here's what I meant about@unutbu's answer only working with local defined classes&MDASH;and which using EDOCX1,12,instead of EDOCX1,13,13,would make it work with any accessible class,not only those defined in the current scop

对于那些用eval()表示不满的人来说,一种方式也是要避免的。

第一个例子是利用vars()说明潜在问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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:

1
2
3
4
5
6
7
8
9
10
11
12
# 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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'>]


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

1
2
3
4
5
6
7
8
from itertools import chain

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


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

我们当然可以在访问对象本身的情况下轻松地做到这一点,是的。

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

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

1
2
3
4
5
6
7
8
9
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

用途:

1
2
3
4
5
6
7
8
9
10
11
12
>>> 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'>]

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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], [])

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

1
2
3
4
class Ham(object):
    pass

assert(issubclass(Ham, Ham)) # True

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

1
2
3
4
5
6
7
8
9
10
11
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], [])

这不是一个很好的答案,比如使用特殊建筑工程师1〔9〕类方法,这类方法@unutbu mentions,所以我将它作为一种练习。函数定义返回一个字典,该字典映射所有子类的名称,并将它们归还给子类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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)

输出

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

我无法想象它的真实世界用例,但一种健壮的方法(即使是在Python2旧样式类上)是扫描全局名称空间:

1
2
3
4
5
6
7
8
9
10
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经典类。