Given a base class named ParentClass
, how can I find all leaf subclasses of it? For example, if I have:
class ParentClass:
pass
class SubClass1(ParentClass):
pass
class SubClass2(ParentClass):
pass
class SubClass3(ParentClass):
pass
class SubClass4(SubClass2):
pass
class SubClass5(SubClass2):
pass
class SubClass6(SubClass2, SubClass3):
pass
I want a list containing, [SubClass1, SubClass4, SubClass5, SubClass6]
, and not SubClass2
or SubClass3
as they are not leaves in the inheritance hierarchy.
Since the problem is that you have an arbitrary hierarchy, and just want the subclassmost to be "concrete" classes, and having a registry for them, maybe requiring, in one of the base classes, that these concrete classes set a class attribute that all concrete implementations should have, but that would not make sense for intermediate classes (I can imagine a card .name
attribute, for example).
The usual mechanism of Abstract Base Classes for Python can work for this, with the required attribute overriding a method in the baseclass marked as @abstractmethod
and an __init_subclass__
method in the same base can then add that class to a registry with your desired leave classes (And even check if a class marked as "leave" by setting name
had been further subclassed, raising an error)
class Base(ABC):
# All concrete classes must set a "name" attribute
name = abstractmethod(lambda:None)
_card_registry = set()
def __init_subclass__(cls, *args, **kw):
if cls.name is not __class__.name: # subclass overwrote ".name"
# optional - check if no other superclass of cls, other than this one, set name:
for supercls in cls.__mro__[1:]:
if supercls is __class__:
break
if getattr(supercls, "name") is not __class__.name:
raise TypeError(f"{supercls.__name__} class setting ´.name´ was further subclassed")
__class__._card_registry.add(cls)
super().__init_subclass__(*args, **kw)
And after importing all your modules, Base._card_registry
would contain all classes that interest you.
Now, the literal answer for getting all the leafmost subclasses, although not the best pattern in this specific case:
Python will allow one to retrieve class' direct subclasses with the .__subclasses__
method. A simple code to retrieve all current leave classes could be:
def getleaves(cls, leaves=None):
if leaves is None: leaves=set()
if subclasses:=cls.__subclasses__():
for subclass in subclasses:
getleaves(subclass, leaves)
else:
leaves.add(cls)
return leaves