我怎样才能find一个名字的所有子类?

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

新风格的类(也就是Python 3中默认的object子类)有一个返回子类的__subclasses__方法:

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

这里是子类的名字:

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

这里是子类本身:

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

确认这个子类确实把Foo列为他们的基础:

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

注意,如果你想要子类,你将不得不recursion:

 def all_subclasses(cls): return cls.__subclasses__() + [g for s in cls.__subclasses__() for g in all_subclasses(s)] print(all_subclasses(vars()['Foo'])) # [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>] 

如果你只是想直接子类然后.__subclasses__()工作正常。 如果你想要所有的子类,子类的子类等等,你需要一个函数来为你做。

这是一个简单的,可读的函数,recursion地find给定类的所有子类:

 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 

一般forms的最简单的解决scheme:

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

如果你有一个类,你inheritance的类方法:

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

Python 3.6__init_subclass__

正如提到的其他答案,你可以检查__subclasses__属性来获取子类的列表,因为python 3.6你可以通过重写__init_subclass__方法来修改这个属性的创build。

 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__的行为,并从这个列表中省略/添加子类。

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

获取所有子类列表的简短版本:

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

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

这是一个没有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], []) 

这与其他实现不同,因为它返回原始类。 这是因为它使代码更简单:

 class Ham(object): pass assert(issubclass(Ham, Ham)) # True 

如果get_subclasses_gen看起来有点奇怪,那是因为它是通过将尾recursion实现转换为循环生成器来创build的:

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

我无法想象一个真实世界的用例,但是一个可靠的方法(即使在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 经典

我怎样才能find一个名字的所有子类?

是的,我们当然可以轻松地做到这一点。

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

我为另一个答案创build了一个实现,因为它回答了这个问题,并且比其他解决scheme稍微优雅一些​​,这里是:

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