Ich brauche einen funktionierenden Ansatz, um alle Klassen zu erhalten, die von einer Basisklasse in Python geerbt wurden.
Ich brauche einen funktionierenden Ansatz, um alle Klassen zu erhalten, die von einer Basisklasse in Python geerbt wurden.
Antworten:
Klassen neuen Stils (dh Unterklassen von object
, was in Python 3 die Standardeinstellung ist) haben eine __subclasses__
Methode, die die Unterklassen zurückgibt:
class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass
Hier sind die Namen der Unterklassen:
print([cls.__name__ for cls in Foo.__subclasses__()])
# ['Bar', 'Baz']
Hier sind die Unterklassen selbst:
print(Foo.__subclasses__())
# [<class '__main__.Bar'>, <class '__main__.Baz'>]
Bestätigung, dass die Unterklassen tatsächlich Foo
als Basis aufgeführt sind:
for cls in Foo.__subclasses__():
print(cls.__base__)
# <class '__main__.Foo'>
# <class '__main__.Foo'>
Hinweis: Wenn Sie Unterklassen möchten, müssen Sie Folgendes wiederholen:
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'>}
Beachten Sie, dass wenn die Klassendefinition einer Unterklasse noch nicht ausgeführt wurde - beispielsweise wenn das Modul der Unterklasse noch nicht importiert wurde - diese Unterklasse noch nicht vorhanden ist und __subclasses__
sie nicht findet.
Sie haben "seinen Namen gegeben" erwähnt. Da Python-Klassen erstklassige Objekte sind, müssen Sie anstelle der Klasse oder dergleichen keine Zeichenfolge mit dem Klassennamen verwenden. Sie können die Klasse einfach direkt verwenden, und Sie sollten es wahrscheinlich tun.
Wenn Sie eine Zeichenfolge haben, die den Namen einer Klasse darstellt, und die Unterklassen dieser Klasse suchen möchten, gibt es zwei Schritte: Suchen Sie die Klasse mit ihrem Namen und suchen Sie dann die Unterklassen mit __subclasses__
wie oben.
Wie Sie die Klasse anhand des Namens finden, hängt davon ab, wo Sie sie erwarten. Wenn Sie erwarten, dass es sich im selben Modul befindet wie der Code, der versucht, die Klasse zu finden, dann
cls = globals()[name]
würde den Job machen, oder in dem unwahrscheinlichen Fall, dass Sie erwarten, ihn bei Einheimischen zu finden,
cls = locals()[name]
Wenn sich die Klasse in einem Modul befinden könnte, sollte Ihre Namenszeichenfolge den vollständig qualifizierten Namen enthalten - so etwas wie 'pkg.module.Foo'
statt nur 'Foo'
. Verwenden Sie importlib
diese Option , um das Modul der Klasse zu laden und dann das entsprechende Attribut abzurufen:
import importlib
modname, _, clsname = name.rpartition('.')
mod = importlib.import_module(modname)
cls = getattr(mod, clsname)
Wie auch immer Sie die Klasse finden, cls.__subclasses__()
würde dann eine Liste ihrer Unterklassen zurückgeben.
Wenn Sie nur direkte Unterklassen möchten, .__subclasses__()
funktioniert dies einwandfrei. Wenn Sie alle Unterklassen, Unterklassen von Unterklassen usw. möchten, benötigen Sie eine Funktion, die dies für Sie erledigt.
Hier ist eine einfache, lesbare Funktion, die rekursiv alle Unterklassen einer bestimmten Klasse findet:
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
ein sein set
, um Duplikate zu beseitigen?
A(object)
, B(A)
, C(A)
, und D(B, C)
. get_all_subclasses(A) == [B, C, D, D]
.
Die einfachste Lösung in allgemeiner Form:
def get_subclasses(cls):
for subclass in cls.__subclasses__():
yield from get_subclasses(subclass)
yield subclass
Und eine Klassenmethode für den Fall, dass Sie eine einzelne Klasse haben, von der Sie erben:
@classmethod
def get_subclasses(cls):
for subclass in cls.__subclasses__():
yield from subclass.get_subclasses()
yield subclass
__init_subclass__
Wie bereits erwähnt, können Sie das __subclasses__
Attribut überprüfen , um die Liste der Unterklassen abzurufen. Seit Python 3.6 können Sie diese Attributerstellung ändern, indem Sie die __init_subclass__
Methode überschreiben .
class PluginBase:
subclasses = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.subclasses.append(cls)
class Plugin1(PluginBase):
pass
class Plugin2(PluginBase):
pass
Auf diese Weise können Sie, wenn Sie wissen, was Sie tun, das Verhalten von __subclasses__
Unterklassen überschreiben und in dieser Liste weglassen / hinzufügen.
__init_subclass
die Klasse der Eltern auslösen .
Hinweis: Ich sehe, dass jemand (nicht @unutbu) die referenzierte Antwort so geändert hat, dass sie nicht mehr verwendet wird. Daher vars()['Foo']
gilt der primäre Punkt meines Beitrags nicht mehr.
FWIW, hier ist, was ich damit gemeint habe, dass die Antwort von @ unutbu nur mit lokal definierten Klassen funktioniert - und dass die Verwendung von eval()
statt vars()
mit jeder zugänglichen Klasse funktioniert, nicht nur mit den im aktuellen Bereich definierten.
Für diejenigen, die es nicht mögen eval()
, wird auch ein Weg gezeigt, dies zu vermeiden.
Hier ist zunächst ein konkretes Beispiel, das das potenzielle Problem bei der Verwendung veranschaulicht 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'>]
Dies könnte verbessert werden, indem das eval('ClassName')
Down in die definierte Funktion verschoben wird, was die Verwendung erleichtert, ohne dass die zusätzliche Allgemeinheit verloren geht, die durch die Verwendung erzielt wird, eval()
die im Gegensatz zu vars()
nicht kontextsensitiv ist:
# 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'>]
Schließlich ist es möglich und in einigen Fällen sogar wichtig, die Verwendung eval()
aus Sicherheitsgründen zu vermeiden. Hier ist eine Version ohne diese:
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()
- jetzt besser?
Eine viel kürzere Version, um eine Liste aller Unterklassen zu erhalten:
from itertools import chain
def subclasses(cls):
return list(
chain.from_iterable(
[list(chain.from_iterable([[x], subclasses(x)])) for x in cls.__subclasses__()]
)
)
Wie finde ich alle Unterklassen einer Klasse mit ihrem Namen?
Wir können dies sicherlich leicht tun, wenn wir auf das Objekt selbst zugreifen, ja.
Einfach seinen Namen zu geben, ist eine schlechte Idee, da es mehrere Klassen desselben Namens geben kann, die sogar im selben Modul definiert sind.
Ich habe eine Implementierung für eine andere Antwort erstellt . Da diese Frage beantwortet wird und etwas eleganter ist als die anderen Lösungen hier, ist sie hier:
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
Verwendung:
>>> 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'>]
Dies ist keine so gute Antwort wie die Verwendung der speziellen integrierten __subclasses__()
Klassenmethode, die @unutbu erwähnt, daher präsentiere ich sie lediglich als Übung. Die subclasses()
definierte Funktion gibt ein Wörterbuch zurück, das alle Unterklassennamen den Unterklassen selbst zuordnet.
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)
Ausgabe:
{'SubclassB': <class '__main__.SubclassB'>,
'SubclassA': <class '__main__.SubclassA'>}
Hier ist eine Version ohne Rekursion:
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], [])
Dies unterscheidet sich von anderen Implementierungen darin, dass die ursprüngliche Klasse zurückgegeben wird. Dies liegt daran, dass der Code dadurch einfacher wird und:
class Ham(object):
pass
assert(issubclass(Ham, Ham)) # True
Wenn get_subclasses_gen etwas seltsam aussieht, liegt das daran, dass es durch Konvertieren einer rekursiven Implementierung in einen Schleifengenerator erstellt wurde:
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], [])