Abstract attribute (not property)? Abstract attribute (not property)? python python

Abstract attribute (not property)?


It's 2018, we deserve a bit better solution:

from better_abc import ABCMeta, abstract_attribute    # see belowclass AbstractFoo(metaclass=ABCMeta):    @abstract_attribute    def bar(self):        passclass Foo(AbstractFoo):    def __init__(self):        self.bar = 3class BadFoo(AbstractFoo):    def __init__(self):        pass

It will behave like this:

Foo()     # okBadFoo()  # will raise: NotImplementedError: Can't instantiate abstract class BadFoo# with abstract attributes: bar

This answer uses same approach as the accepted answer, but integrates well with built-in ABC and does not require boilerplate of check_bar() helpers.

Here is the better_abc.py content:

from abc import ABCMeta as NativeABCMetaclass DummyAttribute:    passdef abstract_attribute(obj=None):    if obj is None:        obj = DummyAttribute()    obj.__is_abstract_attribute__ = True    return objclass ABCMeta(NativeABCMeta):    def __call__(cls, *args, **kwargs):        instance = NativeABCMeta.__call__(cls, *args, **kwargs)        abstract_attributes = {            name            for name in dir(instance)            if getattr(getattr(instance, name), '__is_abstract_attribute__', False)        }        if abstract_attributes:            raise NotImplementedError(                "Can't instantiate abstract class {} with"                " abstract attributes: {}".format(                    cls.__name__,                    ', '.join(abstract_attributes)                )            )        return instance

The nice thing is that you can do:

class AbstractFoo(metaclass=ABCMeta):    bar = abstract_attribute()

and it will work same as above.

Also one can use:

class ABC(ABCMeta):    pass

to define custom ABC helper. PS. I consider this code to be CC0.

This could be improved by using AST parser to raise earlier (on class declaration) by scanning the __init__ code, but it seems to be an overkill for now (unless someone is willing to implement).

2021: typing support

You can use:

from typing import cast, Any, Callable, TypeVarR = TypeVar('R')def abstract_attribute(obj: Callable[[Any], R] = None) -> R:    _obj = cast(Any, obj)    if obj is None:        _obj = DummyAttribute()    _obj.__is_abstract_attribute__ = True    return cast(R, _obj)

which will let mypy highlight some typing issues

class AbstractFooTyped(metaclass=ABCMeta):    @abstract_attribute    def bar(self) -> int:        passclass FooTyped(AbstractFooTyped):    def __init__(self):        # skipping assignment (which is required!) to demonstrate        # that it works independent of when the assignment is made        passf_typed = FooTyped()_ = f_typed.bar + 'test'   # Mypy: Unsupported operand types for + ("int" and "str")FooTyped.bar = 'test'    # Mypy: Incompatible types in assignment (expression has type "str", variable has type "int")FooTyped.bar + 'test'    # Mypy: Unsupported operand types for + ("int" and "str")

and for the shorthand notation, as suggested by @SMiller in the comments:

class AbstractFooTypedShorthand(metaclass=ABCMeta):    bar: int = abstract_attribute()AbstractFooTypedShorthand.bar += 'test'   # Mypy: Unsupported operand types for + ("int" and "str")


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()  # okBadFoo ()  # yield NotImplementedError


Just because you define it as an abstractproperty on the abstract base class doesn't mean you have to make a property on the subclass.

e.g. you can:

In [1]: from abc import ABCMeta, abstractpropertyIn [2]: class X(metaclass=ABCMeta):   ...:     @abstractproperty   ...:     def required(self):   ...:         raise NotImplementedError   ...:In [3]: class Y(X):   ...:     required = True   ...:In [4]: Y()Out[4]: <__main__.Y at 0x10ae0d390>

If you want to initialise the value in __init__ you can do this:

In [5]: class Z(X):   ...:     required = None   ...:     def __init__(self, value):   ...:         self.required = value   ...:In [6]: Z(value=3)Out[6]: <__main__.Z at 0x10ae15a20>

Since Python 3.3 abstractproperty is deprecated. So Python 3 users should use the following instead:

from abc import ABCMeta, abstractmethodclass X(metaclass=ABCMeta):    @property    @abstractmethod    def required(self):        raise NotImplementedError