如何找到给定名称的类的所有子类?
Posted
技术标签:
【中文标题】如何找到给定名称的类的所有子类?【英文标题】:How to find all the subclasses of a class given its name? 【发布时间】:2011-04-21 05:03:09 【问题描述】:我需要一种工作方法来获取从 Python 中的基类继承的所有类。
【问题讨论】:
【参考方案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)
当类层次结构是有偏树时。
【讨论】:
【参考方案2】:这不如使用@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'>
【讨论】:
【参考方案3】:注意:我看到有人(不是 @unutbu)更改了引用的答案,因此它不再使用 vars()['Foo']
— 所以我的帖子的主要观点不再适用。
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'>]
【讨论】:
【参考方案4】:新型类(即从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__()
都会返回其子类的列表。
【讨论】:
假设我想查找一个模块中的所有子类,无论包含它的模块的子模块是否已被导入? @SamanthaAtkins:生成一个list of all submodules of the package,然后生成一个list of all classes for each module。 谢谢,这就是我最终做的,但我很好奇是否有更好的方法我错过了。【参考方案5】:如何找到给定名称的类的所有子类?
如果可以访问对象本身,我们当然可以轻松地做到这一点,是的。
仅仅给出它的名字是一个糟糕的主意,因为可以有多个同名的类,甚至在同一个模块中定义。
我为另一个 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'>]
【讨论】:
【参考方案6】:
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__
的行为并从此列表中省略/添加子类。
【讨论】:
是的,任何类型的任何子类都会触发父类的__init_subclass
。【参考方案7】:
这是一个没有递归的版本:
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】:获取所有子类列表的更短版本:
from itertools import chain
def subclasses(cls):
return list(
chain.from_iterable(
[list(chain.from_iterable([[x], subclasses(x)])) for x in cls.__subclasses__()]
)
)
【讨论】:
【参考方案9】:一般形式的最简单解决方案:
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
【讨论】:
生成器的方法真的很干净。【参考方案10】:如果您只想要直接子类,那么 .__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
【讨论】:
谢谢@fletom!尽管那时我需要的只是 __subclasses__() 您的解决方案非常好。带你 +1 ;) 顺便说一句,我认为在你的情况下使用生成器可能更可靠。 不应该all_subclasses
是set
以消除重复吗?
@RyneEverett 你的意思是如果你使用多重继承?我认为否则你不应该以重复结束。
@fletom 是的,重复项需要多重继承。例如,A(object)
、B(A)
、C(A)
和 D(B, C)
。 get_all_subclasses(A) == [B, C, D, D]
.
@RomanPrykhodchenko:你的问题的标题说要找到一个给定其名称的类的所有子类,但这以及其他唯一的工作给定类本身,而不仅仅是它的名称 - 所以只是什么是吗?以上是关于如何找到给定名称的类的所有子类?的主要内容,如果未能解决你的问题,请参考以下文章