我怎样才能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'>]