Equivalent of NotImplementedError for fields in Python

Question:

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.

Asked By: Kiv

||

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
Answered By: Evan Fosmark
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.

Answered By: Glenn Maynard

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.

Answered By: Glenn Maynard

And here is my solution:

def not_implemented_method(func):
    from functools import wraps
    from inspect import getargspec, formatargspec

    @wraps(func)
    def wrapper(self, *args, **kwargs):
        c = self.__class__.__name__
        m = func.__name__
        a = formatargspec(*getargspec(func))
        raise NotImplementedError(''%s' object does not implement the method '%s%s'' % (c, m, a))

    return wrapper


def not_implemented_property(func):
    from functools import wraps
    from inspect import getargspec, formatargspec

    @wraps(func)
    def wrapper(self, *args, **kwargs):
        c = self.__class__.__name__
        m = func.__name__
        raise NotImplementedError(''%s' object does not implement the property '%s'' % (c, m))

    return property(wrapper, wrapper, wrapper)

It can be used as

class AbstractBase(object):
    @not_implemented_method
    def test(self):
        pass

    @not_implemented_property
    def value(self):
        pass

class Implementation(AbstractBase):
    value = None

    def __init__(self):
        self.value = 42

    def test(self):
        return True
Answered By: fwyzard

A better way to do this is using Abstract Base Classes:

import abc

class Foo(abc.ABC):

    @property
    @abc.abstractmethod
    def demo_attribute(self):
        raise NotImplementedError

    @abc.abstractmethod
    def demo_method(self):
        raise NotImplementedError

class BadBar(Foo):
    pass

class GoodBar(Foo):

    demo_attribute = 'yes'

    def demo_method(self):
        return self.demo_attribute

bad_bar = BadBar()
# TypeError: Can't instantiate abstract class BadBar 
# with abstract methods demo_attribute, demo_method

good_bar = GoodBar()
# OK

Note that you should still have raise NotImplementedError instead of something like pass, because there is nothing preventing the inheriting class from calling super().demo_method(), and if the abstract demo_method is just pass, this will fail silently.

Answered By: ostrokach

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
Answered By: moppag

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.

Answered By: smarie

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)
Answered By: Mark Mishyn
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.