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.
An abstract property declaration does not provide an implementation of the property accessors -- it declares that the class supports properties, but leaves the accessor implementation to derived classes. The following example demonstrates how to implement the abstract properties inherited from a base class.
An abstract class is a special type of class that cannot be instantiated and acts as a base class for other classes. Abstract class members marked as abstract must be implemented by derived classes. An abstract class is a special type of class that cannot be instantiated and acts as a base class for other classes.
How to declare abstract properties in C# An abstract property is declared by using the abstract modifier in a property declaration to indicate that the property is an abstract method and does not contain implementation.
Abstract classes make sure that derived classes implement methods and properties defined in the abstract base class. Abstract base class can't be instantiated. We use @abstractmethod to define a method in the abstract base class and combination of @property and @abstractmethod in order to define an abstract property.
A possibly a bit better solution compared to the accepted answer:
from better_abc import ABCMeta, abstract_attribute # see below class AbstractFoo(metaclass=ABCMeta): @abstract_attribute def bar(self): pass class Foo(AbstractFoo): def __init__(self): self.bar = 3 class BadFoo(AbstractFoo): def __init__(self): pass
It will behave like this:
Foo() # ok BadFoo() # 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 NativeABCMeta class DummyAttribute: pass def abstract_attribute(obj=None): if obj is None: obj = DummyAttribute() obj.__is_abstract_attribute__ = True return obj class 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).
You can use:
from typing import cast, Any, Callable, TypeVar R = 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: pass class FooTyped(AbstractFooTyped): def __init__(self): # skipping assignment (which is required!) to demonstrate # that it works independent of when the assignment is made pass f_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 love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With