Forcing Python subclasses to define 'required' superclass attributes

Today I will show you a quick way of forcing any subclass of a superclass to define required attributes. I was recently writing a software for my PhD. I was developing a framework that could be useful for other programmers in the future. However, this framework is quite generic and is not implementing any case-specific or robot-specific behaviours. Therefore, I am expecting from the programmers using this framework to specify the required attributes that are too specific to go in the superclass.

I found a nice way of throwing an error if any subclass is not defining the required attributes at object instantiation level (i.e during object instantiation). This way, we can make sure that the attributes are defined and we can get rid of some conditional statements from our code (i.e if self.x is not None:).

Have a quick look at the code below:

As you can see, at line 23 we are using the __metaclass__ (Python 2) or line 31 metaclass= (Python 3) in ForceRequiredAttributeDefinition to define a metaclass (the ForceRequiredAttributeDefinitionMeta) so Python will use this metaclass to create the class.

Figure 1: How instances are created in Python [1]

In the metaclass, we override the magic method __call__. As you can see from Figure 1, first the __call__ method is called when creating a class instance which in turn is calling the class’s __new__ and __init__ before returning the instance to the caller. So this interferes with the creation of class’s instances. This is exactly what we want. We would like after __init__ of the instance to call a method to check if required attributes have been defined. This is done on line 18 of the metaclass.

The class ForceRequiredAttributeDefinition has to implements the check_required_attributes() method which in turn has to check if the ‘required’ attributes actually exist (Note that we define the required attributes as None first). In the case where the required attribute is None, we are raising a NotImplementedError with an appropriate message to let the user know about the required attribute.

You can also see a demonstration using the subclass ConcereteValidExample which defines the required attribute starting_day_of_week and another subclass, ConcereteInvalidExample which does not define the required attribute starting_day_of_week and throws an NotImplementedError at instantiation level.

Here is the output of running the script above:

Traceback (most recent call last):
  File "test.py", line 50, in <module>
    ConcereteInvalidExample()  # This will throw an NotImplementedError straightaway
  File "test.py", line 18, in __call__
    obj.check_required_attributes()
  File "test.py", line 36, in check_required_attributes
    raise NotImplementedError('Subclass must define self.starting_day_of_week attribute. \n This attribute should define the first day of the week.')
NotImplementedError: Subclass must define self.starting_day_of_week attribute.
 This attribute should define the first day of the week.

As you can see the ConcereteValidExample was successful but the ConcereteInvalidExample raised an NotImplementedError.

This may not be the “Pythonic” way of solving the problem, however, it just works. With all that said, I am still looking for a nicer way of solving this particular problem. If I do find something I will let you know.

References

[1] https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/

© 2019 Rafael Papallas
Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.