Saya membutuhkan pendekatan kerja untuk mendapatkan semua kelas yang diwarisi dari kelas dasar dengan Python.
Saya membutuhkan pendekatan kerja untuk mendapatkan semua kelas yang diwarisi dari kelas dasar dengan Python.
Jawaban:
Kelas gaya baru (yaitu subclass dari object
, yang merupakan default di Python 3) memiliki __subclasses__
metode yang mengembalikan subclass:
class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass
Berikut adalah nama-nama subclass:
print([cls.__name__ for cls in Foo.__subclasses__()])
# ['Bar', 'Baz']
Berikut adalah subkelasnya sendiri:
print(Foo.__subclasses__())
# [<class '__main__.Bar'>, <class '__main__.Baz'>]
Konfirmasi bahwa subclass memang mendaftar Foo
sebagai basis mereka:
for cls in Foo.__subclasses__():
print(cls.__base__)
# <class '__main__.Foo'>
# <class '__main__.Foo'>
Catatan jika Anda ingin subkelas, Anda harus mengulang:
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'>}
Perhatikan bahwa jika definisi kelas dari subclass belum dieksekusi - misalnya, jika modul subclass belum diimpor - maka subclass itu belum ada, dan __subclasses__
tidak akan menemukannya.
Anda menyebutkan "diberi nama". Karena kelas Python adalah objek kelas satu, Anda tidak perlu menggunakan string dengan nama kelas sebagai pengganti kelas atau semacamnya. Anda bisa menggunakan kelas secara langsung, dan Anda mungkin harus melakukannya.
Jika Anda memiliki string yang mewakili nama kelas dan Anda ingin menemukan subclass kelas itu, maka ada dua langkah: temukan kelas yang diberi nama, dan kemudian temukan subclass dengan __subclasses__
seperti di atas.
Cara menemukan kelas dari nama tergantung pada tempat Anda mengharapkannya. Jika Anda berharap menemukannya dalam modul yang sama dengan kode yang mencoba menemukan kelas, maka
cls = globals()[name]
akan melakukan pekerjaan itu, atau dalam kasus yang tidak Anda harapkan untuk menemukannya di penduduk setempat,
cls = locals()[name]
Jika kelas bisa dalam modul apa pun, maka string nama Anda harus berisi nama yang sepenuhnya memenuhi syarat - sesuatu seperti 'pkg.module.Foo'
bukan hanya 'Foo'
. Gunakan importlib
untuk memuat modul kelas, lalu ambil atribut yang sesuai:
import importlib
modname, _, clsname = name.rpartition('.')
mod = importlib.import_module(modname)
cls = getattr(mod, clsname)
Namun Anda menemukan kelas, cls.__subclasses__()
maka akan mengembalikan daftar subkelasnya.
Jika Anda hanya ingin subclass langsung maka .__subclasses__()
berfungsi dengan baik. Jika Anda ingin semua subclass, subclass dari subclass, dan sebagainya, Anda akan memerlukan fungsi untuk melakukannya untuk Anda.
Inilah fungsi sederhana dan mudah dibaca yang secara rekursif menemukan semua subclass dari kelas yang diberikan:
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
menjadi set
untuk menghilangkan duplikat?
A(object)
, B(A)
, C(A)
, dan D(B, C)
. get_all_subclasses(A) == [B, C, D, D]
.
Solusi paling sederhana dalam bentuk umum:
def get_subclasses(cls):
for subclass in cls.__subclasses__():
yield from get_subclasses(subclass)
yield subclass
Dan metode kelas jika Anda memiliki satu kelas di mana Anda mewarisi dari:
@classmethod
def get_subclasses(cls):
for subclass in cls.__subclasses__():
yield from subclass.get_subclasses()
yield subclass
__init_subclass__
Seperti jawaban lain yang disebutkan, Anda dapat memeriksa __subclasses__
atribut untuk mendapatkan daftar subclass, karena python 3.6 Anda dapat memodifikasi pembuatan atribut ini dengan mengganti __init_subclass__
metode.
class PluginBase:
subclasses = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.subclasses.append(cls)
class Plugin1(PluginBase):
pass
class Plugin2(PluginBase):
pass
Dengan cara ini, jika Anda tahu apa yang Anda lakukan, Anda dapat mengesampingkan perilaku __subclasses__
dan menghilangkan / menambahkan subclass dari daftar ini.
__init_subclass
pada kelas induknya.
Catatan: Saya melihat bahwa seseorang (bukan @unutbu) mengubah jawaban yang direferensikan sehingga tidak lagi menggunakan vars()['Foo']
- sehingga titik utama dari posting saya tidak berlaku lagi.
FWIW, inilah yang saya maksudkan tentang jawaban @ unutbu hanya bekerja dengan kelas yang ditentukan secara lokal - dan bahwa menggunakan eval()
bukannya vars()
akan membuatnya bekerja dengan kelas yang dapat diakses, tidak hanya yang didefinisikan dalam lingkup saat ini.
Bagi mereka yang tidak suka menggunakan eval()
, cara juga ditunjukkan untuk menghindarinya.
Pertama, inilah contoh nyata yang menunjukkan potensi masalah dengan penggunaan 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'>]
Ini dapat ditingkatkan dengan memindahkan fungsi eval('ClassName')
ke bawah ke dalam fungsi yang didefinisikan, yang membuatnya lebih mudah tanpa kehilangan generalitas tambahan yang diperoleh dengan menggunakan eval()
yang tidak seperti vars()
itu tidak peka konteks:
# 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'>]
Terakhir, itu mungkin, dan mungkin bahkan penting dalam beberapa kasus, untuk menghindari penggunaan eval()
karena alasan keamanan, jadi inilah versi tanpa itu:
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()
- lebih baik sekarang?
Versi yang jauh lebih pendek untuk mendapatkan daftar semua subclass:
from itertools import chain
def subclasses(cls):
return list(
chain.from_iterable(
[list(chain.from_iterable([[x], subclasses(x)])) for x in cls.__subclasses__()]
)
)
Bagaimana saya bisa menemukan semua subclass dari kelas yang diberi namanya?
Kita tentu bisa dengan mudah melakukan ini mengingat akses ke objek itu sendiri, ya.
Cukup diberi namanya adalah ide yang buruk, karena mungkin ada beberapa kelas dengan nama yang sama, bahkan didefinisikan dalam modul yang sama.
Saya membuat implementasi untuk jawaban lain , dan karena menjawab pertanyaan ini dan itu sedikit lebih elegan daripada solusi lain di sini, ini dia:
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
Pemakaian:
>>> 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'>]
Ini bukan jawaban yang sebaik menggunakan __subclasses__()
metode kelas bawaan yang @unutbu sebutkan, jadi saya menyajikannya hanya sebagai latihan. The subclasses()
fungsi yang didefinisikan kembali sebuah kamus yang memetakan semua nama subclass ke subclass sendiri.
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)
Keluaran:
{'SubclassB': <class '__main__.SubclassB'>,
'SubclassA': <class '__main__.SubclassA'>}
Inilah versi tanpa rekursi:
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], [])
Ini berbeda dari implementasi lain karena mengembalikan kelas asli. Ini karena membuat kode lebih sederhana dan:
class Ham(object):
pass
assert(issubclass(Ham, Ham)) # True
Jika get_subclasses_gen terlihat sedikit aneh itu karena itu dibuat dengan mengubah implementasi tail-recursive menjadi generator looping:
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], [])