How to create an abstract class attribute (potentially read-only)

abstract-class, class-properties, exception, python

Solution

I came up with a solution based on those posted earlier. (Thank you @Daniel Roseman and @martineau)

I created a metaclass called ABCAMeta (the last 'A' stands for 'Attributes').

The class has two ways of working.

A class which just uses ABCAMeta as a metaclass must have a property called `required_attributes` which should contain a list of the names of all the attributes you want to require on future subclasses of that class

A class whose parent's metaclass is ABCAMeta must have all the required attributes specified by its parent class(es).

For example:

class AbstractFoo(metaclass=ABCAMeta):
    required_attributes = ['force_this']

class RealFoo(AbstractFoo):
    pass

will throw an error:

`NameError: Class 'RealFoo' has not implemented the following attributes: 'force_this'`

Exactly how I wanted.

from abc import ABCMeta

class NoRequirements(RuntimeError):
        def __init__(self, message):
            RuntimeError.__init__(self, message)

class ABCAMeta(ABCMeta):
    def __init__(mcls, name, bases, namespace):
        ABCMeta.__init__(mcls, name, bases, namespace)

    def __new__(mcls, name, bases, namespace):
        def get_requirements(c):
            """c is a class that should have a 'required_attributes' attribute
            this function will get that list of required attributes or
            raise a NoRequirements error if it doesn't find one.
            """

            if hasattr(c, 'required_attributes'):
                return c.required_attributes
            else:
                raise NoRequirements(f"Class '{c.__name__}' has no 'required_attributes' property")

        cls = super().__new__(mcls, name, bases, namespace)
        # true if no parents of the class being created have ABCAMeta as their metaclass
        basic_metaclass = True
        # list of attributes the class being created must implement
        # should stay empty if basic_metaclass stays True
        reqs = []
        for parent in bases:
            parent_meta = type(parent)
            if parent_meta==ABCAMeta:
                # the class being created has a parent whose metaclass is ABCAMeta
                # the class being created must contain the requirements of the parent class
                basic_metaclass=False
                try:
                    reqs.extend(get_requirements(parent))
                except NoRequirements:
                    raise
        # will force subclasses of the created class to define
        # the attributes listed in the required_attributes attribute of the created class
        if basic_metaclass:
            get_requirements(cls) # just want it to raise an error if it doesn't have the attributes
        else:
            missingreqs = []
            for req in reqs:
                if not hasattr(cls, req):
                    missingreqs.append(req)
            if len(missingreqs)!=0:
                raise NameError(f"Class '{cls.__name__}' has not implemented the following attributes: {str(missingreqs)[1:-1]}")
        return cls

Any suggestions for improvement are welcome in the comments.

Problem

I have spent a lot of time researching this, but none of the answers seem to work how I would like. I have an abstract class with a class attribute I want each subclass to be forced to implement ``` class AbstractFoo(): forceThis = 0 ``` So that when I do this ``` class RealFoo(AbstractFoo): pass ``` it throws an error telling me it can't create the class until I implement `forceThis`. How can I do that? (I don't want the attribute to be read-only, but if that's the only solution, I'll accept it.) For a class method, I've discovered I can do ``` from abc import ABCMeta, abstractmethod class AbstractFoo(metaclass=ABCMeta): @classmethod @abstractmethod def forceThis(): """This must be implemented""" ``` so that ``` class RealFoo(AbstractFoo): pass ``` at least throws the error `TypeError: Can't instantiate abstract class EZ with abstract methods forceThis` (Although it doesn't force `forceThis` to be a class method.) How can I get a similar error to pop up for the class attribute?

Original source

Related problems