Pythonの基本クラスから継承されるすべてのクラスを取得する実用的なアプローチが必要です。
Pythonの基本クラスから継承されるすべてのクラスを取得する実用的なアプローチが必要です。
回答:
新しいスタイルのクラス(つまり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クラスはファーストクラスのオブジェクトなので、クラスの代わりにクラス名などの文字列を使用する必要はありません。クラスを直接使用できますが、おそらく使用する必要があります。
クラスの名前を表す文字列があり、そのクラスのサブクラスを検索したい場合は、2つのステップがあります。名前を指定してクラスを検索し、次に__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__()
そのサブクラスのリストが返されます。
直接サブクラスが必要な場合は、問題なく.__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
all_subclasses
ありませんset
か?
A(object)
、B(A)
、C(A)
、とD(B, C)
。get_all_subclasses(A) == [B, C, D, D]
。
一般的な形式での最も簡単なソリューション:
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
__init_subclass__
他の回答が述べたように、__subclasses__
属性をチェックしてサブクラスのリストを取得できます。Python3.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
が親のクラスをトリガーします。
注:(@unutbuではなく)誰かが参照された回答を変更して、それが使用されないようにしたvars()['Foo']
ので、私の投稿の主なポイントは適用されなくなりました。
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): # 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'>]
eval()
—今より良いですか?
すべてのサブクラスのリストを取得するためのはるかに短いバージョン:
from itertools import chain
def subclasses(cls):
return list(
chain.from_iterable(
[list(chain.from_iterable([[x], subclasses(x)])) for x in cls.__subclasses__()]
)
)
名前を指定してクラスのすべてのサブクラスを見つけるにはどうすればよいですか?
もちろん、オブジェクト自体へのアクセスがあれば、簡単にこれを行うことができます。
同じモジュールで定義されていても、同じ名前のクラスが複数存在する可能性があるため、単にその名前を付けるのはお勧めできません。
私は別の回答の実装を作成しました。これはこの質問に回答し、他のソリューションよりも少しエレガントであるため、ここにあります:
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'>]
これは__subclasses__()
、@ unutbuが言及している特別な組み込みクラスメソッドを使用するほど良い答えではないので、単に演習として提示します。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'>}
これは再帰のないバージョンです:
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], [])