Equivalent of NotImplementedError for fields in Python

Viewed 41075

In Python 2.x when you want to mark a method as abstract, you can define it like so:

class Base:
    def foo(self):
        raise NotImplementedError("Subclasses should implement this!")

Then if you forget to override it, you get a nice reminder exception. Is there an equivalent way to mark a field as abstract? Or is stating it in the class docstring all you can do?

At first I thought I could set the field to NotImplemented, but when I looked up what it's actually for (rich comparisons) it seemed abusive.

8 Answers

Yes, you can. Use the @property decorator. For instance, if you have a field called "example" then can't you do something like this:

class Base(object):

    @property
    def example(self):
        raise NotImplementedError("Subclasses should implement this!")

Running the following produces a NotImplementedError just like you want.

b = Base()
print b.example

Alternate answer:

@property
def NotImplementedField(self):
    raise NotImplementedError

class a(object):
    x = NotImplementedField

class b(a):
    # x = 5
    pass

b().x
a().x

This is like Evan's, but concise and cheap--you'll only get a single instance of NotImplementedField.

def require_abstract_fields(obj, cls):
    abstract_fields = getattr(cls, "abstract_fields", None)
    if abstract_fields is None:
        return

    for field in abstract_fields:
        if not hasattr(obj, field):
            raise RuntimeError, "object %s failed to define %s" % (obj, field)

class a(object):
    abstract_fields = ("x", )
    def __init__(self):
        require_abstract_fields(self, a)

class b(a):
    abstract_fields = ("y", )
    x = 5
    def __init__(self):
        require_abstract_fields(self, b)
        super(b, self).__init__()

b()
a()

Note the passing of the class type into require_abstract_fields, so if multiple inherited classes use this, they don't all validate the most-derived-class's fields. You might be able to automate this with a metaclass, but I didn't dig into that. Defining a field to None is accepted.

It seem that this question was open to both instance attributes and class attributes, I'll focus on the first topic only.

So, for instance attributes, an alternate answer to Evan's is to define a mandatory field using pyfields:

from pyfields import field

class Base(object):
    example = field(doc="This should contain an example.")

b = Base()
b.example

yields

pyfields.core.MandatoryFieldInitError: 
   Mandatory field 'example' has not been initialized yet 
   on instance <__main__.Base object at 0x000002C1000C0C18>.

Granted, it does not provide you with the ability to edit the error message by talking about subclasses. But in a way it is more realistic to not talk about subclasses - indeed in python, attributes can be overridden on instances of the base class - not only in subclasses.

Note: I'm the author of pyfields. See documentation for details.

Here is a simple example how to set required properties/methods for sublasses in Python 3.

class Base:
    requires = ('foo', 'bar')

    def __init_subclass__(cls, **kwargs):
        for requirement in cls.requires:
            if not hasattr(cls, requirement):
                raise NotImplementedError(
                        f'"{cls.__name__}" must have "{requirement}".')
        super().__init_subclass__(**kwargs)

An interesting pattern to handle this is to set attribute to None in the parent class and to access the attribute with a function that ensure it has been set in the child class.

Here is an example from django-rest-framework:

class GenericAPIView(views.APIView):

    [...]

    serializer_class = None

    [...]

    def get_serializer_class(self):
        assert self.serializer_class is not None, (
            "'%s' should either include a `serializer_class` attribute, "
            "or override the `get_serializer_class()` method."
            % self.__class__.__name__
        )

        return self.serializer_class
Related