我需要一种工作方法来获取从Python中的基类继承的所有类。
新风格(I.E.subclassed from EDOCX1&0),which is the default in Python 3)have a EDOCX1&1)method which returns the subclasses:
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的亚类:
print(Foo.__subclasses__())
# [, ]
2.确认次类为Foo的指数列表:
for cls in Foo.__subclasses__():
print(cls.__base__)
#
#
如果你想要补助金,你必须退还:
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))
# {, , }
注:如果一个亚类的分类定义没有被执行,例如,亚类的模块没有被进口,那么亚类不存在,__subclasses__将无法找到。
你说过"给他名字"自从Python类是第一类物体,你不需要用一条带有该类物体名称的条纹来代替该类物体或任何类似物体。你可以直接使用班级,你也许应该。
如果你有一个字符串代表一个班的名称,你想找到这个班的亚班,那么就有两步:找到它的名字,然后用__subclasses__找到亚班。
如何从名称中找到这一类取决于你打算在哪找到它。如果你指望在同一个模块中找到它作为代码
cls = globals()[name]
你可以做这份工作,或者在你指望在当地找到的不寻常的情况下,
cls = locals()[name]
如果班级可以在任何模块中,那么你的名字应该包含完全合格的名字使用importlib载荷舱模块,然后检索相应的属性:
import importlib
modname, _, clsname = name.rpartition('.')
mod = importlib.import_module(modname)
cls = getattr(mod, clsname)
你怎么找到班级,cls.__subclasses__()
你真的需要vars()['Foo'].__subclasses__()吗?难道江户记1〔1〕不是不那么蹩脚的等价物,还是我遗漏了什么?
@马特·罗昂戈:在评论这个问题时,OP说,"I need a function that takes a name of some class and returns a list of classes"。
我同意@matt luongo。不管OP在他们的评论中说了什么,在没有任何争论的情况下称为vars()等同于locals(),因此,正如您的回答所示,暗示使用vars()['Foo']的微妙之处在于,它意味着class Foo是在本地范围内定义的。这意味着,任何一个vars()['Foo'].__subclasses__()可以工作的地方,Foo.__subclasses__()也可以工作。
刚才碰巧注意到您对使用set来避免重复的修订,但是,我无法想象会有一个类继承树包含它们。既然,我想你是有理由做出改变的,你能解释一下动机吗?
@马蒂诺:复制的一种可能出现的方式是,如果有人定义了class Bing(Bar, Foo): pass。然后,原来的all_subclasses(Foo)将返回Bing两次。这看起来像是一个愚蠢的角落案例,但我认为通过使用set对代码进行愚蠢的校对没有什么害处,从而保证了我们想要的行为。
@用户2357112:感谢您的巨大改进。
如果你只想直接上亚班,那么就做得好。如果你想要所有的亚类,亚类,那么你需要一个功能来为你。
这是一个简单的、可实现的功能,它可归纳出一个吉普赛人的所有亚类:
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!虽然我需要回到那些日子,只是uu子类uu(),你的解决方案真的很好。以你+1;)顺便说一句,我认为在你的情况下使用发电机可能更可靠。
为了消除重复,all_subclasses不应该是set吗?
@Ryneverett你的意思是如果你使用多重继承?我想不然你不应该以重复结尾。
@fletom是的,对于副本,需要多重继承。例如,A(object)、B(A)、C(A)和D(B, C)。get_all_subclasses(A) == [B, C, D, D]。
@RomanPrykhodchenko:你问题的题目是找到一个类的所有子类,给出了它的名字,但是这个类以及其他唯一的工作给出了类本身,而不仅仅是它的名字,那么它是什么?
The simplest solution in general form:
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
发电机的方法非常干净。
Python3.6
作为另一个答案,你可以检查__subclasses__〔1〕自从Python3.6你可以通过EDOCX1〕〔20〕来修改这个属性的产生。
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的亚类。
这对子类的间接作用吗?
是的,任何种类的子类都会触发父类的__init_subclass。
FWIW,here's what I meant about@unutbu's answer only working with local defined classes—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()说明潜在问题:
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
# -> [, , ]
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
# -> [, , ]
# using eval('xxx') instead of vars()['xxx']
def func2():
print(all_subclasses(eval('Foo')))
func2() # Works
# -> [, , ]
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:
# 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()
# -> [, , ]
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:
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
# -> [, , ]
对这种事情使用eval()和vars()是非常邪恶的…
@克里斯:增加了一个不使用eval()的版本——现在更好了?
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__()]
)
)
How can I find all subclasses of a class given its name?
我们当然可以在访问对象本身的情况下轻松地做到这一点,是的。
简单地给出它的名字是一个糟糕的主意,因为可以有多个同名的类,甚至在同一个模块中定义。
我为另一个答案创建了一个实现,因为它回答了这个问题,而且比这里的其他解决方案更优雅,所以这里是:
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)
[,
,
,
,
,
,
,
,
]
这里有一个没有递归的版本:
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_子类_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], [])
这不是一个很好的答案,比如使用特殊建筑工程师1〔9〕类方法,这类方法@unutbu mentions,所以我将它作为一种练习。函数定义返回一个字典,该字典映射所有子类的名称,并将它们归还给子类。
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': ,
'SubclassA': }
我无法想象它的真实世界用例,但一种健壮的方法(即使是在Python2旧样式类上)是扫描全局名称空间:
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经典类。