Abstract Attributes in Python

Abstract attributes in Python

Python has a built-in exception for this, though you won't encounter the exception until runtime.

class Base(object):
@property
def path(self):
raise NotImplementedError

class SubClass(Base):
path = 'blah'

Abstract attribute (not property)?

If you really want to enforce that a subclass define a given attribute, you can use metaclasses:

 class AbstractFooMeta(type):

def __call__(cls, *args, **kwargs):
"""Called when you call Foo(*args, **kwargs) """
obj = type.__call__(cls, *args, **kwargs)
obj.check_bar()
return obj


class AbstractFoo(object):
__metaclass__ = AbstractFooMeta
bar = None

def check_bar(self):
if self.bar is None:
raise NotImplementedError('Subclasses must define bar')


class GoodFoo(AbstractFoo):
def __init__(self):
self.bar = 3


class BadFoo(AbstractFoo):
def __init__(self):
pass

Basically the meta class redefine __call__ to make sure check_bar is called after the init on an instance.

GoodFoo()  # ok
BadFoo ()  # yield NotImplementedError

How to create an abstract attribute for an abstract class in Python?

The way to enforce this is not with abstractmethod, but via __init_subclass__.

class DataFilter:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
try:
cls.filter_name
except AttributeError:
raise TypeError("Can't instantiate class DataFilter without name 'filter_name'")

You can perform additional tests on the value of cls.filter_name if it is present.

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

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.

  1. 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

  2. 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.

How to create abstract properties in python abstract classes?

Since Python 3.3 a bug was fixed meaning the property() decorator is now correctly identified as abstract when applied to an abstract method.

Note: Order matters, you have to use @property above @abstractmethod

Python 3.3+: (python docs):

from abc import ABC, abstractmethod

class C(ABC):
@property
@abstractmethod
def my_abstract_property(self):
...

Python 2: (python docs)

from abc import ABC, abstractproperty

class C(ABC):
@abstractproperty
def my_abstract_property(self):
...

Python Abstract Attribute

You're expecting each child class to have self.requirements right? So change the following code to this.

class Event(object):
@property
def requirements(self):
try:
return self._requirements
except AttributeError:
raise NotImplementedError('subclasses must have requirements')

That way it will return self.requirements. If self.requirements hasn't been implemented by the child class it will raise a not implemented error.

EDIT: Updated return to avoid never-ending loop.



Related Topics



Leave a reply



Submit