Search code examples
pythonclasspropertiesabstract-class

Abstract Base Class property setter absence not preventing Class instantiation


I'm trying to get abstract properties to work, enforcing property getter & setter definitions in downstream classes.

from abc import ABC, abstractmethod


class BaseABC(ABC):
    @property
    @abstractmethod
    def x(self):
        pass

    @x.setter
    @abstractmethod
    def x(self, value):
        pass


class MyClass(BaseABC):
    def __init__(self, value):
        self._x = value
    
    @property
    def x(self):
        return self._x

    # @x.setter
    # def x(self, val):
    #     self._x = val


obj = MyClass(10)
print(obj.x)
obj.x = 20
print(obj.x)

Having read the documentation it seems to indicate the above should trigger a TypeError, when the class is being build, but it only triggers an AttributeError once the attribute is being set.

Why does the absent setter, explicitly defined in BaseABC through an @abstractmethod, not trigger the expected TypeError? How does one ensure a setter is required in the daughter class?


Solution

  • TL;DR property doesn't just override the getter; it overrides the setter with None as well.


    In MyClass, property creates a brand new property with the given getter and no setter; it doesn't simply override the getter of the inherited property. The definition of MyClass.x is equivalent to

    def x_getter(self):
         return self._x
    
    x = property(x_getter, None)
    

    and that None as a setter is "good enough" as far as ABC is concerned with respect to overriding the abstract setter.

    To "inherit" the abstract setter as well, use

    class MyClass(BaseABC):
    
        @BaseABC.x.getter
        def x(self):
            return self._x
    

    This creates a new property not from scratch, but from the existing BaseABC.x property, using its (abstract) setter but a new getter (just like x.setter before created a new property using the old getter but a new setter).

    To make MyClass instantiable, you still need to provide a concrete setter using x.setter.


    Unfortunately, nothing forces you to to use BaseABC.x.getter in place of property. ABC only cares that x gets set to something appropriate. This is a general problem with ABC, not properties in particular. There is only so much you can do at the library level; abstract base classes are not a feature of Python itself.