Как найти все подclassы classа с его именем?

Мне нужен рабочий подход для получения всех classов, унаследованных от базового classа в Python.

Классы нового стиля (т.е. подclassы из object , который является значением по умолчанию в Python 3), имеют метод __subclasses__ который возвращает подclassы:

 class Foo(object): pass class Bar(Foo): pass class Baz(Foo): pass class Bing(Bar): pass 

Вот имена подclassов:

 print([cls.__name__ for cls in vars()['Foo'].__subclasses__()]) # ['Bar', 'Baz'] 

Вот сами подclassы:

 print(vars()['Foo'].__subclasses__()) # [, ] 

Подтверждение того, что подclassы действительно перечисляют Foo качестве основы:

 for cls in vars()['Foo'].__subclasses__(): print(cls.__base__) #  #  

Обратите внимание, что если вы хотите subsubclasses, вам придется рекурсивно:

 def all_subclasses(cls): return set(cls.__subclasses__()).union( [s for c in cls.__subclasses__() for s in all_subclasses(c)]) print(all_subclasses(vars()['Foo'])) # {, , } 

Если вам просто нужны прямые подclassы, то .__subclasses__() отлично работают. Если вам нужны все подclassы, подclassы подclassов и т. Д., Вам понадобится функция для этого.

Вот простая, читаемая функция, которая рекурсивно находит все подclassы данного classа:

 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 в случае, если у вас есть один class, на который вы наследуете:

 @classmethod def get_subclasses(cls): for subclass in cls.__subclasses__(): yield from subclass.get_subclasses() yield subclass 

Python 3.6__init_subclass__

В качестве другого ответа вы можете проверить атрибут __subclasses__ чтобы получить список подclassов, поскольку 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__ и опустить / добавить подclassы из этого списка.

FWIW, вот что я имел в виду, что ответ @ unutbu работает только с локально определенными classами, и что использование eval() вместо vars() позволит ему работать с любым доступным classом, а не только с теми, которые определены в текущей области.

Для тех, кто не любит использовать 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 # -> [, , ] 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 # -> [, , ] # using eval('xxx') instead of vars()['xxx'] def func2(): print(all_subclasses(eval('Foo'))) func2() # Works # -> [, , ] 

Это можно улучшить, перемещая 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() # -> [, , ] 

Наконец, возможно, и, возможно, даже важно в некоторых случаях избегать использования 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ов:

 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() возвращает словарь, который отображает все подclassы в сами подclassы.

 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': , '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. Это связано с тем, что код упрощает и:

 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], []) 

Я не могу представить себе реальный случай использования в мире, но надежный способ (даже в classах старого стиля 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 новых classах стилей и classах Python 3, а также на classических classах Python 2

Как найти все подclassы classа с его именем?

Мы можем, конечно, легко сделать это, учитывая доступ к самому объекту, да.

Просто его название – плохая идея, так как могут быть несколько classов с одним и тем же именем, даже определенные в одном модуле.

Я создал реализацию для другого ответа , и поскольку он отвечает на этот вопрос, и он немного более изящный, чем другие решения здесь, вот он:

 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) [, , , , , , , , ] 
  • ssl.SSLError: версия протокола предупреждения tlsv1
  • Как я могу назвать функцию Javascript из Python?
  • Интуиция и идея перестройки 4D-массива в 2D-массив в NumPy
  • Каковы преимущества NumPy над регулярными списками Python?
  • Как нормализовать 2-мерный массив numpy в python менее подробный?
  • переносимый способ записи csv-файла в python 2 или python 3
  • selenium.common.exceptions.WebDriverException: Сообщение: исполняемый файл «chromedriver» должен быть в ошибке PATH с безголовым Chrome
  • Почему понимание списка намного быстрее, чем добавление списка?
  • Как работает метод pandas groupby?
  • Pandas groupby.size vs series.value_counts vs collections.Counter с несколькими сериями
  • Чтение файлов xlsx с использованием Python
  • Давайте будем гением компьютера.