pythonabstract

Abstract attribute (not property)?


What's the best practice to define an abstract instance attribute, but not as a property?

I would like to write something like:

class AbstractFoo(metaclass=ABCMeta):

    @property
    @abstractmethod
    def bar(self):
        pass

class Foo(AbstractFoo):

    def __init__(self):
        self.bar = 3

Instead of:

class Foo(AbstractFoo):

    def __init__(self):
        self._bar = 3

    @property
    def bar(self):
        return self._bar

    @bar.setter
    def setbar(self, bar):
        self._bar = bar

    @bar.deleter
    def delbar(self):
        del self._bar

Properties are handy, but for simple attribute requiring no computation they are an overkill. This is especially important for abstract classes which will be subclassed and implemented by the user (I don't want to force someone to use @property when he just could have written self.foo = foo in the __init__).

Abstract attributes in Python question proposes as only answer to use @property and @abstractmethod: it doesn't answer my question.

The ActiveState recipe for an abstract class attribute via AbstractAttribute may be the right way, but I am not sure. It also only works with class attributes and not instance attributes.


Solution

  • 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