10 ответов:
классы нового стиля (т. е. подклассы из
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'>]подтверждение того, что подклассы действительно как и их основа:
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 являются объектами первого класса, вам не нужно использовать строку с именем класса вместо класса или что-то в этом роде. Вы можете просто использовать класс напрямую, и вы наверное, стоит.
если у вас есть строка, представляющая имя класса, и вы хотите найти подклассы этого класса, то есть два шага: найти класс с заданным именем, а затем найти подклассы с
__subclasses__как выше.как найти класс от имени зависит от того, где вы ожидаете его найти. Если вы ожидаете найти его в том же модуле, что и код, который пытается найти класс, то
cls = globals()[name]сделал бы работу, или в маловероятном случае, что вы ожидаете найти его в местных жителях,
cls = locals()[name]если класс может быть в любом модуле, то ваша строка имени должна содержать полное имя - что-то вроде просто
'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
самое простое решение в общем виде:
def get_subclasses(cls): for subclass in cls.__subclasses__(): yield from get_subclasses(subclass) yield subclassи classmethod в в случае, если у вас есть один класс, где вы наследуете от:
@classmethod def get_subclasses(cls): for subclass in cls.__subclasses__(): yield from subclass.get_subclasses() yield subclass
Python 3.6 -
__init_subclass__Как упоминалось в другом ответе, вы можете проверить
__subclasses__атрибут, чтобы получить список подклассов, так как python 3.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__и исключить/добавить подклассы из этого списка.
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'>]
гораздо более короткая версия для получения списка всех подклассов:
from itertools import chain def subclasses(cls): return list( chain.from_iterable( [list(chain.from_iterable([[x], subclasses(x)])) for x in cls.__subclasses__()] ) )
это не так хорошо, ответ как с помощью специальных встроенных
__subclasses__()метод класса, который упоминает @unutbu, поэтому я представляю его просто как упражнение. Элементsubclasses()функция defined возвращает словарь, который сопоставляет все имена подклассов самим подклассам.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], [])
Я не могу представить себе реальный случай использования для него, но надежный способ (даже на Python 2 старых классов стиля) будет сканировать пространство имен globals:
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 классический классы
как я могу найти все подклассы класса с заданным именем?
мы, конечно, можем легко сделать это, учитывая доступ к самому объекту, да.
просто учитывая его имя-плохая идея, так как может быть несколько классов с одинаковым именем, даже определенных в одном модуле.
Я создал реализацию для другой ответ, и, поскольку она отвечает на этот вопрос и это немного более элегантно, чем другие решения здесь, вот это:
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'>]
Comments