Мне нужен рабочий подход для получения всех классов, которые унаследованы от базового класса в Python.
Классы нового стиля (то есть подклассы из 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'>]
Подтверждение того, что подклассы действительно указывают в Foo
качестве своей базы:
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]
Если класс может находиться в каком-либо модуле, тогда ваша строка имени должна содержать полное имя - что-то вроде, 'pkg.module.Foo'
а не просто 'Foo'
. Используйте importlib
для загрузки модуля класса, а затем получите соответствующий атрибут:
import importlib
modname, _, clsname = name.rpartition('.')
mod = importlib.import_module(modname)
cls = getattr(mod, clsname)
Однако вы найдете класс, cls.__subclasses__()
который затем вернет список его подклассов.
Если вы просто хотите прямые подклассы, то
.__subclasses__()
отлично работает. Если вам нужны все подклассы, подклассы подклассов и т. Д., Вам понадобится функция, которая сделает это за вас.Вот простая читаемая функция, которая рекурсивно находит все подклассы данного класса:
источник
all_subclasses
быть,set
чтобы устранить дубликаты?A(object)
,B(A)
,C(A)
иD(B, C)
.get_all_subclasses(A) == [B, C, D, D]
,Самое простое решение в общем виде:
И метод класса, если у вас есть один класс, из которого вы наследуете:
источник
Python 3.6 -
__init_subclass__
Как уже упоминалось в другом ответе, вы можете проверить
__subclasses__
атрибут, чтобы получить список подклассов, так как в python 3.6 вы можете изменить создание этого атрибута, переопределив__init_subclass__
метод.Таким образом, если вы знаете, что делаете, вы можете переопределить поведение
__subclasses__
и опустить / добавить подклассы из этого списка.источник
__init_subclass
класс родителя.Примечание: я вижу, что кто-то (не @unutbu) изменил ссылочный ответ, чтобы он больше не использовался
vars()['Foo']
- поэтому основной пункт моего поста больше не применяется.Кстати, вот что я хотел сказать об ответе @ unutbu, работающем только с локально определенными классами - и что использование
eval()
вместоvars()
заставило бы его работать с любым доступным классом, а не только с теми, которые определены в текущей области видимости.Для тех, кто не любит использовать
eval()
, также показан способ избежать этого.Сначала приведу конкретный пример, демонстрирующий потенциальную проблему с использованием
vars()
:Это может быть улучшено путем перемещения
eval('ClassName')
вниз в определенную функцию, что упрощает ее использование без потери дополнительной универсальности, получаемой при использовании функцииeval()
отличие отvars()
контекста:Наконец, возможно, и, возможно, даже важно, в некоторых случаях, избегать использования
eval()
по соображениям безопасности, поэтому вот версия без нее:источник
eval()
- лучше сейчас?Гораздо более короткая версия для получения списка всех подклассов:
источник
Конечно, мы можем легко сделать это при наличии доступа к самому объекту, да.
Просто учитывая его имя - плохая идея, так как может быть несколько классов с одним и тем же именем, даже определенных в одном модуле.
Я создал реализацию для другого ответа , и поскольку он отвечает на этот вопрос, и он немного более элегантен, чем другие решения, вот он:
Использование:
источник
Это не такой хороший ответ, как использование специального встроенного
__subclasses__()
метода класса, о котором упоминает @unutbu, поэтому я представляю его просто как упражнение. Определеннаяsubclasses()
функция возвращает словарь, который отображает все имена подклассов на сами подклассы.Вывод:
источник
Вот версия без рекурсии:
Это отличается от других реализаций тем, что возвращает исходный класс. Это потому, что это делает код проще и:
Если get_subclasses_gen выглядит немного странно, то это потому, что он был создан путем преобразования хвостовой рекурсивной реализации в генератор циклов:
источник