Python'da bir temel sınıftan miras alınan tüm sınıfları almak için çalışan bir yaklaşıma ihtiyacım var.
Python'da bir temel sınıftan miras alınan tüm sınıfları almak için çalışan bir yaklaşıma ihtiyacım var.
Yanıtlar:
Yeni stil sınıflarının (yani alt sınıf, object
Python 3'te varsayılan) __subclasses__
alt sınıfları döndüren bir yöntemi vardır:
class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass
Alt sınıfların isimleri:
print([cls.__name__ for cls in Foo.__subclasses__()])
# ['Bar', 'Baz']
İşte alt sınıfların kendileri:
print(Foo.__subclasses__())
# [<class '__main__.Bar'>, <class '__main__.Baz'>]
Alt sınıfların Foo
temel olarak listelediklerini teyit etme:
for cls in Foo.__subclasses__():
print(cls.__base__)
# <class '__main__.Foo'>
# <class '__main__.Foo'>
Alt sınıflar istiyorsanız, tekrarlamanız gerektiğini unutmayın:
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'>}
Bir alt sınıfın sınıf tanımı henüz yürütülmediyse (örneğin, alt sınıfın modülü henüz içe aktarılmadıysa), o alt sınıfın henüz mevcut olmadığını ve __subclasses__
onu bulamayacağını unutmayın.
"İsmi verildi" den bahsettiniz. Python sınıfları birinci sınıf nesneler olduğundan, sınıfın yerine sınıfın adını taşıyan bir dize veya bunun gibi bir şey kullanmanız gerekmez. Sınıfı doğrudan kullanabilirsiniz ve muhtemelen kullanmalısınız.
Bir sınıfın adını temsil eden bir dizeniz varsa ve bu sınıfın alt sınıflarını bulmak istiyorsanız, iki adım vardır: adı verilen sınıfı bulun ve __subclasses__
yukarıdaki gibi alt sınıfları bulun .
Sınıfın adından nasıl bulunacağı, onu nerede bulmayı beklediğinize bağlıdır. Sınıfı bulmaya çalışan kodla aynı modülde bulmayı bekliyorsanız, o zaman
cls = globals()[name]
işi yapar ya da yerel bir yerde bulmayı beklediğiniz olası bir durumda,
cls = locals()[name]
Sınıf herhangi modülünde olabilir, o zaman adın dize tam nitelikli adını içermelidir - gibi bir şey 'pkg.module.Foo'
yerine sadece 'Foo'
. importlib
Sınıfın modülünü yüklemek için kullanın , ardından ilgili niteliği alın:
import importlib
modname, _, clsname = name.rpartition('.')
mod = importlib.import_module(modname)
cls = getattr(mod, clsname)
Ancak sınıfı bulursanız, cls.__subclasses__()
alt sınıflarının bir listesini döndürürsünüz.
Sadece doğrudan alt sınıf istiyorsanız o zaman .__subclasses__()
iyi çalışıyor. Tüm alt sınıfları, alt sınıfların alt sınıflarını vb. İstiyorsanız, bunu sizin için yapacak bir işleve ihtiyacınız olacaktır.
İşte belirli bir sınıfın tüm alt sınıflarını özyineli olarak bulan basit, okunabilir bir işlev:
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
için bir olmamalı set
?
A(object)
, B(A)
, C(A)
, ve D(B, C)
. get_all_subclasses(A) == [B, C, D, D]
.
Genel formdaki en basit çözüm:
def get_subclasses(cls):
for subclass in cls.__subclasses__():
yield from get_subclasses(subclass)
yield subclass
Tek bir sınıfınız olması durumunda, aşağıdakilerden miras aldığınız bir sınıf yöntemi:
@classmethod
def get_subclasses(cls):
for subclass in cls.__subclasses__():
yield from subclass.get_subclasses()
yield subclass
__init_subclass__
Bahsedilen diğer cevaplarda __subclasses__
, alt sınıfların listesini almak için özniteliği kontrol edebilirsiniz , çünkü python 3.6, bu özniteliğin oluşturulmasını __init_subclass__
yöntemi geçersiz kılarak değiştirebilirsiniz .
class PluginBase:
subclasses = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.subclasses.append(cls)
class Plugin1(PluginBase):
pass
class Plugin2(PluginBase):
pass
Bu şekilde, ne yaptığınızı biliyorsanız __subclasses__
, bu listenin alt sınıflarının davranışını geçersiz kılabilir ve alt sınıfları atlayabilirsiniz.
__init_subclass
, ebeveynin sınıfını tetikler .
Not: Birinin (@unutbu değil) başvurulan yanıtı artık kullanmaması için değiştirdiğini görüyorum - bu yüzden yazımın vars()['Foo']
birincil noktası artık geçerli değil.
FWIW, @ unutbu'nun sadece yerel olarak tanımlanmış sınıflarla çalışmasının cevabı hakkında ne demek istediğimi - ve bunun eval()
yerine kullanmanın vars()
sadece mevcut kapsamda tanımlananlarla değil, erişilebilir herhangi bir sınıfla çalışmasını sağlayacağını düşünüyorum.
Kullanmayı sevmeyenler eval()
için bundan kaçınmanın bir yolu da gösterilmiştir.
İlk olarak, kullanımdaki potansiyel sorunu gösteren somut bir örnek 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'>]
Bu, eval('ClassName')
aşağıya tanımlanan fonksiyona taşınarak geliştirilebilir , bu da içeriğin eval()
aksine vars()
içeriğe duyarlı olmayan kazanılmış ek genelliği kaybetmeden kullanmayı kolaylaştırır :
# 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'>]
Son olarak, eval()
güvenlik nedeniyle kullanmaktan kaçınmak ve hatta bazı durumlarda önemli olabilir , bu yüzden onsuz bir sürüm:
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()
- şimdi daha iyi mi?
Tüm alt sınıfların bir listesini almak için çok daha kısa bir sürüm:
from itertools import chain
def subclasses(cls):
return list(
chain.from_iterable(
[list(chain.from_iterable([[x], subclasses(x)])) for x in cls.__subclasses__()]
)
)
Adı verilen bir sınıfın tüm alt sınıflarını nasıl bulabilirim?
Nesnenin kendisine verilen bu erişimi kesinlikle kolayca yapabiliriz, evet.
Aynı modülde tanımlanmış olsa bile, aynı isimde birden fazla sınıf olabileceğinden, basitçe adı kötü bir fikirdir.
Başka bir cevap için bir uygulama oluşturdum ve bu soruya cevap verdiğinden ve buradaki diğer çözümlerden biraz daha zarif olduğundan, burada:
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
Kullanımı:
>>> 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'>]
Bu, __subclasses__()
@unutbu'nun bahsettiği özel yerleşik sınıf yöntemini kullanmak kadar iyi bir cevap değil , bu yüzden sadece bir egzersiz olarak sunuyorum. subclasses()
Fonksiyon alt sınıfları kendileri için tüm alt sınıf adlarını eşleyen bir sözlük döner tanımladı.
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)
Çıktı:
{'SubclassB': <class '__main__.SubclassB'>,
'SubclassA': <class '__main__.SubclassA'>}
İşte özyinelemesiz bir sürüm:
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], [])
Bu, orijinal sınıfı döndürmesi bakımından diğer uygulamalardan farklıdır. Bunun nedeni, kodu daha basit hale getirmesi ve:
class Ham(object):
pass
assert(issubclass(Ham, Ham)) # True
Get_subclasses_gen biraz garip görünüyorsa, bunun nedeni kuyruk yinelemeli bir uygulamayı döngü oluşturucuya dönüştürerek oluşturulmuş olmasıdır:
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], [])