Как найти все подclassы classа с его именем?
Мне нужен рабочий подход для получения всех classов, унаследованных от базового classа в Python.
- Безопасный метод Python для получения значения вложенного словаря
- Автоматически инициализировать переменные экземпляра?
- Beautiful Soup findAll не находит их всех
- Как читать несколько строк исходного ввода в Python?
- Scikit Learn OneHotEncoder подходит и преобразуется Ошибка: ValueError: X имеет другую форму, чем во время установки
- Как связать ключ ввода с функцией в tkinter?
- Преобразование Unicode в ASCII без ошибок в Python
- Обрабатывать несколько окон в 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) [, , , , , , , , ]