Eu preciso de uma abordagem de trabalho para obter todas as classes que são herdadas de uma classe base em Python.
As classes de novo estilo (por exemplo, subclassificadas de object
, que é o padrão no Python 3) têm um __subclasses__
método que retorna as subclasses:
class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass
Aqui estão os nomes das subclasses:
print([cls.__name__ for cls in Foo.__subclasses__()])
# ['Bar', 'Baz']
Aqui estão as próprias subclasses:
print(Foo.__subclasses__())
# [<class '__main__.Bar'>, <class '__main__.Baz'>]
Confirmação de que as subclasses realmente listam Foo
como sua base:
for cls in Foo.__subclasses__():
print(cls.__base__)
# <class '__main__.Foo'>
# <class '__main__.Foo'>
Observe que se você quiser sub-classes, precisará recursar:
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'>}
Observe que, se a definição de classe de uma subclasse ainda não foi executada - por exemplo, se o módulo da subclasse ainda não foi importado -, essa subclasse ainda não existe e __subclasses__
não a encontra.
Você mencionou "dado o nome". Como as classes Python são objetos de primeira classe, você não precisa usar uma string com o nome da classe no lugar da classe ou algo parecido. Você pode simplesmente usar a classe diretamente e provavelmente deveria.
Se você possui uma string que representa o nome de uma classe e deseja encontrar as subclasses dessa classe, existem duas etapas: encontre a classe com seu nome e, em seguida, encontre as subclasses __subclasses__
como acima.
Como encontrar a classe a partir do nome depende de onde você espera encontrá-la. Se você espera encontrá-lo no mesmo módulo que o código que está tentando localizar a classe,
cls = globals()[name]
faria o trabalho ou, no caso improvável que você espera encontrá-lo nos locais,
cls = locals()[name]
Se a classe puder estar em qualquer módulo, sua string de nome deverá conter o nome completo - algo como, em 'pkg.module.Foo'
vez de apenas 'Foo'
. Use importlib
para carregar o módulo da classe e, em seguida, recupere o atributo correspondente:
import importlib
modname, _, clsname = name.rpartition('.')
mod = importlib.import_module(modname)
cls = getattr(mod, clsname)
No entanto, você encontra a classe e, cls.__subclasses__()
em seguida, retorna uma lista de suas subclasses.
Se você apenas deseja subclasses diretas,
.__subclasses__()
funciona bem. Se você deseja todas as subclasses, subclasses de subclasses e assim por diante, precisará de uma função para fazer isso por você.Aqui está uma função simples e legível que encontra recursivamente todas as subclasses de uma determinada classe:
fonte
all_subclasses
ser umset
para eliminar duplicatas?A(object)
,B(A)
,C(A)
, eD(B, C)
.get_all_subclasses(A) == [B, C, D, D]
.A solução mais simples em geral:
E um método de classe, caso você tenha uma única classe da qual herda:
fonte
Python 3.6 -
__init_subclass__
Como outra resposta mencionada, você pode verificar o
__subclasses__
atributo para obter a lista de subclasses, já que o python 3.6 pode modificar essa criação de atributo substituindo o__init_subclass__
métodoDessa forma, se você souber o que está fazendo, poderá substituir o comportamento de
__subclasses__
e omitir / adicionar subclasses desta lista.fonte
__init_subclass
classe dos pais.Nota: Vejo que alguém (não o @unutbu) alterou a resposta referenciada para que ela não seja mais usada
vars()['Foo']
- portanto, o ponto principal da minha postagem não se aplica mais.FWIW, eis o que eu quis dizer sobre a resposta do @ unutbu apenas trabalhando com classes definidas localmente - e que o uso em
eval()
vez devars()
o faria funcionar com qualquer classe acessível, não apenas com as definidas no escopo atual.Para quem não gosta de usar
eval()
, também é mostrado um meio de evitá-lo.Primeiro, aqui está um exemplo concreto que demonstra o possível problema com o uso
vars()
:Isso pode ser aprimorado movendo o
eval('ClassName')
botão para baixo para a função definida, o que facilita o uso sem perda da generalidade adicional obtida pelo usoeval()
que diferentementevars()
não é sensível ao contexto:Por fim, é possível, e talvez até importante em alguns casos, evitar o uso
eval()
por motivos de segurança, então aqui está uma versão sem ele:fonte
eval()
- melhor agora?Uma versão muito mais curta para obter uma lista de todas as subclasses:
fonte
Certamente, podemos facilmente fazer isso com acesso ao próprio objeto, sim.
Basta dar um nome para ele é uma péssima idéia, pois pode haver várias classes com o mesmo nome, mesmo definidas no mesmo módulo.
Criei uma implementação para outra resposta e, como ela responde a essa pergunta e é um pouco mais elegante que as outras soluções aqui, aqui está:
Uso:
fonte
Esta não é uma resposta tão boa quanto usar o
__subclasses__()
método de classe interno especial que o @unutbu menciona, então a apresento apenas como um exercício. Asubclasses()
função definida retorna um dicionário que mapeia todos os nomes das subclasses para as próprias subclasses.Resultado:
fonte
Aqui está uma versão sem recursão:
Isso difere de outras implementações, pois retorna a classe original. Isso ocorre porque simplifica o código e:
Se get_subclasses_gen parece um pouco estranho, é porque foi criado pela conversão de uma implementação recursiva da cauda em um gerador de loop:
fonte