I'm currently using ABC's to ensure that my child classes implement a specific method (or property, in this particular case). I want to make it impossible to create children of Entity
without implementing similar_entities
:
class Entity(ABC):
@property
@abstractmethod
def similar_entities(self) -> list[str]:
return []
class SubEntity1(Entity):
@property
def similar_entities(self):
return ["a", "b", "c"]
SubEntity1() # Can be instantiated
class SubEntity2(Entity):
pass
SubEntity2() # TypeError
However, I don't actually want to prohibit users from creating instances of the base class Entity
itself; creating a "generic" entity is meaningful in this case, and I would like to use the implementation of similar_entities
defined on the Entity
class itself. I'm only using @abstractmethod
to "idiot-check" myself so that I don't accidentally forget to implement this property on my subclasses.
I know full well that the "correct" way to do this would be to create a stub implementation of Entity
and use that for generic instances instead:
class GenericEntity(Entity):
@property
def similar_entities(self) -> list[str]:
return super().similar_entities
GenericEntity() # ...
But to me this essentially violates DRY, and since I'm working with such a simple and limited context I'm wondering if there's a way to have my cake and eat it too. Is there any way to massage @abstractmethod
to only complain on child-instances? If not, is there some better, more flexible way to establish this contract between my classes? I'm also not opposed to "rolling-my-own" decorator if this is more appropriate in this case.
Here is my code that fits:
similar_entities
on subclasses is allowed.Entity
can be instantiated.similar_entities
property raise TypeError
.class MyMeta(type):
def __init__(self, classname, superclasses, attributedict):
super().__init__(classname, superclasses, attributedict)
if 'similar_entities' not in self.__dict__:
raise TypeError
class Entity(metaclass=MyMeta):
@property
def similar_entities(self):
return []
Example 1:
class SubEntity1(Entity):
@property
def similar_entities(self):
return ["a", "b", "c"]
print(SubEntity1().similar_entities) # ['a', 'b', 'c']
Example 2:
class SubEntity2(Entity):
pass
print(SubEntity2().similar_entities) # TypeError
Example 3:
print(Entity().similar_entities) # []