I have the following code to compare a base class' current (empty) implementation of required functions to its sub-classes, which must implement them in some different way in order to be considered acceptable at runtime. Without using a metaclass=ABCMeta
and implementing @abstractmethod
decorators on these base class methods, how do I go about doing this? For now, I'm writing the following __init_subclass__
hook on my ad-hoc, metaclass-less abstract base classes in multiple places in my project, but it feels wrong.
import inspect
class AbstractThing:
def __init__(self, topic: str, thing: Thing):
thing.subscriptions[topic] = self.on_message
thing.on_connected.append(self.on_connected)
thing.on_disconnected.append(self.on_disconnected)
def __init_subclass__(cls):
required_methods = ['on_connected', 'on_disconnected', 'on_message']
for f in required_methods:
func_source = inspect.getsourcelines(getattr(cls, f))
# if this class no longer inherits from `Object`, the method resolution order will have updated
parent_func_source = inspect.getsourcelines(getattr(cls.__mro__[-2], f))
if func_source == parent_func_source:
raise NotImplementedError(f"You need to override method '{f}' in your class {cls.__name__}")
def on_connected(self, config: dict):
pass
def on_disconnected(self):
pass
def on_message(self, msg: str):
pass
Is there a better way to do this? Bonus points if I can get typechecking errors in my editor while defining sub classes of this AbstractThing
.
Indeed, you should not rely on
inspect.getsourcelines
for any code that should be used in serious contexts (i.e. outside experimentation realm, or tools to deal with the source-code itself)The plain and simple
is
operator is enough to check if the method in a given class is the same as in the base class. (In Python 3. Python 2 users have to take care that methods are retrieved asunbound methods
instead of the raw-functions)Other than that, you are taking several unneeded turns to get to the base-class itself - the little documented and little used special variable
__class__
can help you with that: it is an automatic reference to the class body where it is written (do not mistake withself.__class__
which is a reference to the sub-class instead).From the documentation:
So, while keeping your main approach, the whole thing can be quite simpler:
If you have a complex hierarchy, and will have parent classes with other mandatory methods that the subclasses of those will have to implement - and therefore, can't hard code the needed methods in the
required_methods
, you can still use theabstractmethod
decorator fromabc
, without using theABCMeta
metaclass. All the decorator does is to create an attribute on the method that is checked on the metaclass. Just make the same check in a__init_subclass__
method:Keep in mind this just checks for methods that show up in a class'
dir
. But customizing__dir__
is used seldon enough for it to be reliable - just take care to document that.