Necesito un enfoque funcional para obtener todas las clases que se heredan de una clase base en Python.
Las clases de estilo nuevo (es decir, subclase de object
, que es el valor predeterminado en Python 3) tienen un __subclasses__
método que devuelve las subclases:
class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass
Aquí están los nombres de las subclases:
print([cls.__name__ for cls in Foo.__subclasses__()])
# ['Bar', 'Baz']
Aquí están las subclases mismas:
print(Foo.__subclasses__())
# [<class '__main__.Bar'>, <class '__main__.Baz'>]
Confirmación de que las subclases realmente enumeran Foo
como su base:
for cls in Foo.__subclasses__():
print(cls.__base__)
# <class '__main__.Foo'>
# <class '__main__.Foo'>
Tenga en cuenta que si desea subclases, deberá repetir:
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'>}
Tenga en cuenta que si la definición de clase de una subclase aún no se ha ejecutado, por ejemplo, si el módulo de la subclase aún no se ha importado, entonces esa subclase aún no existe y __subclasses__
no la encontrará.
Usted mencionó "dado su nombre". Dado que las clases de Python son objetos de primera clase, no necesita usar una cadena con el nombre de la clase en lugar de la clase ni nada de eso. Puedes usar la clase directamente, y probablemente deberías.
Si tiene una cadena que representa el nombre de una clase y desea encontrar las subclases de esa clase, entonces hay dos pasos: busque la clase con su nombre y luego las subclases __subclasses__
como se indica arriba.
Cómo encontrar la clase a partir del nombre depende de dónde espera encontrarla. Si espera encontrarlo en el mismo módulo que el código que está tratando de ubicar la clase, entonces
cls = globals()[name]
haría el trabajo, o en el improbable caso de que espere encontrarlo en locales,
cls = locals()[name]
Si la clase podría estar en cualquier módulo, entonces su cadena de nombre debería contener el nombre completo, algo así como en 'pkg.module.Foo'
lugar de solo 'Foo'
. Use importlib
para cargar el módulo de la clase, luego recupere el atributo correspondiente:
import importlib
modname, _, clsname = name.rpartition('.')
mod = importlib.import_module(modname)
cls = getattr(mod, clsname)
Independientemente de cómo encuentre la clase, cls.__subclasses__()
devolvería una lista de sus subclases.
Si solo quieres subclases directas, entonces
.__subclasses__()
funciona bien. Si desea todas las subclases, subclases de subclases, etc., necesitará una función que lo haga por usted.Aquí hay una función simple y legible que encuentra recursivamente todas las subclases de una clase dada:
fuente
all_subclasses
ser unaset
para eliminar duplicados?A(object)
,B(A)
,C(A)
, yD(B, C)
.get_all_subclasses(A) == [B, C, D, D]
.La solución más simple en forma general:
Y un método de clase en caso de que tenga una sola clase de la que herede:
fuente
Python 3.6 -
__init_subclass__
Como se mencionó en otra respuesta, puede verificar el
__subclasses__
atributo para obtener la lista de subclases, ya que python 3.6 puede modificar la creación de este atributo anulando el__init_subclass__
método.De esta manera, si sabe lo que está haciendo, puede anular el comportamiento de
__subclasses__
y omitir / agregar subclases de esta lista.fuente
__init_subclass
clase de los padres.Nota: veo que alguien (no @unutbu) cambió la respuesta a la que se hace referencia para que ya no se use
vars()['Foo']
, por lo que el punto principal de mi publicación ya no se aplica.FWIW, esto es lo que quise decir sobre que la respuesta de @unutbu solo funciona con clases definidas localmente, y que usarla en
eval()
lugar devars()
hacerlo funcionaría con cualquier clase accesible, no solo con las definidas en el alcance actual.Para aquellos que no les gusta usar
eval()
, también se muestra una forma de evitarlo.Primero, aquí hay un ejemplo concreto que demuestra el problema potencial con el uso
vars()
:Esto podría mejorarse moviendo
eval('ClassName')
hacia abajo a la función definida, lo que facilita su uso sin perder la generalidad adicional obtenida al usar loeval()
que, a diferencia,vars()
no es sensible al contexto:Por último, es posible, y quizás incluso importante en algunos casos, evitar el uso
eval()
por razones de seguridad, así que aquí hay una versión sin ella:fuente
eval()
, ¿mejor ahora?Una versión mucho más corta para obtener una lista de todas las subclases:
fuente
Ciertamente podemos hacer esto fácilmente dado el acceso al objeto en sí, sí.
Simplemente dado su nombre es una mala idea, ya que puede haber múltiples clases del mismo nombre, incluso definidas en el mismo módulo.
Creé una implementación para otra respuesta , y dado que responde a esta pregunta y es un poco más elegante que las otras soluciones aquí, aquí está:
Uso:
fuente
Esta no es una respuesta tan buena como usar el
__subclasses__()
método de clase incorporado especial que @unutbu menciona, así que lo presento simplemente como un ejercicio. Lasubclasses()
función definida devuelve un diccionario que asigna todos los nombres de subclase a las propias subclases.Salida:
fuente
Aquí hay una versión sin recursividad:
Esto difiere de otras implementaciones en que devuelve la clase original. Esto se debe a que simplifica el código y:
Si get_subclasses_gen se ve un poco extraño, es porque fue creado al convertir una implementación recursiva de la cola en un generador de bucle:
fuente