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?