I read this very nice documentation on abstract class abc.ABC
. It has this example (shortened by me for the purpose of this question):
import abc
class Base(abc.ABC):
@property
@abc.abstractmethod
def value(self):
return 'Should never reach here'
@value.setter
@abc.abstractmethod
def value(self, new_value):
return
class PartialImplementation(Base): # setter not defined/overridden
@property
def value(self):
return 'Read-only'
To my biggest surprise, PartialImplementation
can be instantiated though it only overrides the getter:
>>> PartialImplementation()
<__main__.PartialImplementation at 0x7fadf4901f60>
Naively, I would have thought that since the interface has two abstract methods both would have to be overridden in any concrete class, which is what is written in the documentation: "Although a concrete class must provide implementations of all abstract methods,...". The resolution must be in that we actually have only one abstract name, value
, that needs to be implemented and that does happen in PartialImplementation
.
Can someone please explain this to me properly?
Also, why would you want to lift the setter to the interface if you are not required to implement it; current implementation does nothing if at all callable on a PartialImplementation
instance.
TL;DR PartialImplementation
is not partially implemented; you defined a new property with a concrete getter and a (logically) concrete setter, instead of supplying only a concrete getter.
Abstract properties are tricky. Your base class has one property, which defines itself as abstract by the presence of the abstract getter and setter, rather than a property that you explicitly defined as abstract.
ABCMeta
determines if a class is abstract by looking for class
attributes with a __isabstractmethod__
attribute set to True
. Base.value
is such an attribute. PartialImplementation.value
is not, because its value
attribute is a brand new property independent of Base.value
, and it consists solely of a concrete getter.
Instead, you need to create a new property that's based on the inherited property, using the appropriate method supplied by the inherited property.
This class is still abstract, because @Base.value.getter
creates
a property with a concrete getter but retaining the original abstract setter.
class PartialImplementation(Base):
@Base.value.getter
def value(self):
return 'Read-only'
This class is concrete and also supplies a read-only property.
class PartialImplementation(Base):
@Base.value.getter
def value(self):
return 'Read-only'
@value.setter
def value(self, v):
# Following the example of the __set__ method
# described in https://docs.python.org/3/howto/descriptor.html#properties
raise AttributeError("property 'value' has no setter")
Note that if we used Base.value.setter
instead of value.setter
, we would have create a new property that only added a concrete setter to the inherited property (which still only has an abstract getter). We want to add the setter to our new property with a concrete getter.
(Also note that while value.setter
can take None
as an argument to "remove" an existing setter, it is not sufficient to override an abstract setter.)
I highly recommend looking at the pure-Python implemeantion of property
in the Descriptor Guide to see how properties work under the hood, and to understand why such care must be taken when trying to manipulate them.
One thing it lacks, though, is the code that manipulates the __isabstractmethod__
attribute to make @property
stackable with @abstractmethod
. Essentially, it would set __isabstractmethod__
on itself if any of its initial components were abstract, and each of getter
, setter
, and deleter
set the attribute to false if the last abstract component were replaced with a concrete one.