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