Enforcing Class Variables in a Subclass Enforcing Class Variables in a Subclass python python

Enforcing Class Variables in a Subclass


Python will already throw an exception if you try to use an attribute that doesn't exist. That's a perfectly reasonable approach, as the error message will make it clear that the attribute needs to be there. It is also common practice to provide reasonable defaults for these attributes in the base class, where possible. Abstract base classes are good if you need to require properties or methods, but they don't work with data attributes, and they don't raise an error until the class is instantiated.

If you want to fail as quickly as possible, a metaclass can prevent the user from even defining the class without including the attributes. The nice thing about a metaclass is that it's inheritable, so if you define it on a base class it is automatically used on any class derived on it.

Here's such a metaclass; in fact, here's a metaclass factory that lets you easily pass in the attribute names you wish to require.

def RequiredAttributes(*required_attrs):    class RequiredAttributesMeta(type):        def __init__(cls, name, bases, attrs):            missing_attrs = ["'%s'" % attr for attr in required_attrs                              if not hasattr(cls, attr)]            if missing_attrs:                raise AttributeError("class '%s' requires attribute%s %s" %                                     (name, "s" * (len(missing_attrs) > 1),                                       ", ".join(missing_attrs)))    return RequiredAttributesMeta

Now to actually define a base class using this metaclass is a bit tricky. You have to define the attributes to define the class, that being the entire point of the metaclass, but if the attributes are defined on the base class they are also defined on any class derived from it, defeating the purpose. So what we'll do is define them (using a dummy value) then delete them from the class afterward.

class Base(object):    __metaclass__ = RequiredAttributes("a", "b" ,"c")    a = b = c = 0del Base.a, Base.b, Base.c

Now if you try to define a subclass, but don't define the attributes:

class Child(Base):    pass

You get:

AttributeError: class 'Child' requires attributes 'a', 'b', 'c'

N.B. I don't have any experience with Google App Engine, so it's possible it already uses a metaclass. In this case, you want your RequiredAttributesMeta to derive from that metaclass, rather than type.


Before describing my solution, let me introduce you how Python class instances are created:

Instance creation in Python

Figure 1: Python Instance creation [1]

Given the above description, you can see that in Python class instances are actually created by a Metaclass. As we can see, when the caller is creating an instance of our class, first the __call__ magic method is called which in turn is calling the __new__ and __init__ of the class and then __cal__ is returning the object instance back to the caller.

With all that said, we can simply try to do our checking if the instance created by __init__ actually defines those "required" attributes.

Metaclass

class ForceRequiredAttributeDefinitionMeta(type):    def __call__(cls, *args, **kwargs):        class_object = type.__call__(cls, *args, **kwargs)        class_object.check_required_attributes()        return class_object

As you can see in __call__ what we do is to create the class object, and then call its check_required_attributes() method which will check if the required attributes have been defined. In there if the required attributes are not defined we should simply throw an error.

Superclass

Python 2

class ForceRequiredAttributeDefinition(object):    __metaclass__ = ForceRequiredAttributeDefinitionMeta    starting_day_of_week = None    def check_required_attributes(self):        if self.starting_day_of_week is None:            raise NotImplementedError('Subclass must define self.starting_day_of_week attribute. \n This attribute should define the first day of the week.')

Python 3

class ForceRequiredAttributeDefinition(metaclass=ForceRequiredAttributeDefinitionMeta):    starting_day_of_week = None    def check_required_attributes(self):        if self.starting_day_of_week is None:            raise NotImplementedError('Subclass must define self.starting_day_of_week attribute. \n This attribute should define the first day of the week.')

Here we define the actual superclass. Three things:

  • Should make use of our metaclass.
  • Should define the required attributes as None see starting_day_of_week = None
  • Should implement the check_required_attributes method that checks if the required attributes are None and if they are to throw a NotImplementedError with a reasonable error message to the user.

Example of a working and non-working subclass

class ConcereteValidExample(ForceRequiredAttributeDefinition):    def __init__(self):        self.starting_day_of_week = "Monday"class ConcereteInvalidExample(ForceRequiredAttributeDefinition):    def __init__(self):        # This will throw an error because self.starting_day_of_week is not defined.        pass

Output

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 first instance created successfully since was defining the required attribute, where the second one raised a NotImplementedError straightaway.


Abstract Base Classes allow to declare a property abstract, which will force all implementing classes to have the property. I am only providing this example for completeness, many pythonistas think your proposed solution is more pythonic.

import abcclass Base(object):    __metaclass__ = abc.ABCMeta    @abc.abstractproperty    def value(self):        return 'Should never get here'class Implementation1(Base):    @property    def value(self):        return 'concrete property'class Implementation2(Base):    pass # doesn't have the required property

Trying to instantiate the first implementing class:

print Implementation1()Out[6]: <__main__.Implementation1 at 0x105c41d90>

Trying to instantiate the second implementing class:

print Implementation2()---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last)<ipython-input-4-bbaeae6b17a6> in <module>()----> 1 Implementation2()TypeError: Can't instantiate abstract class Implementation2 with abstract methods value