Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python abstract setters and getters

I want to write abstract class that will force inheriting classes to implement all methods AND properties in my abstract class.

Additionally I want to use of setters and getters for my abstract property to make my code uncluttered and looking nicely

However, current implementation:

import abc


class Component(metaclass=abc.ABCMeta):

    @property
    @abc.abstractmethod
    def status(self):
        pass

    @property
    @status.setter
    @abc.abstractmethod
    def status(self, value):
        pass

does enforce inheriting class to implement getter for my abstract property getter, but does not enforce creating a property setter (what is exactly what I want)

How can I achieve this behavior without loosing all benefits from application of further mentioned method (aka writing new methods and executing them in my abstract class setter) ?

like image 502
Jakub Pastuszuk Avatar asked Aug 15 '26 09:08

Jakub Pastuszuk


1 Answers

The problem is that neither the getter nor the setter is a method of your abstract class; they are attributes of the property, which is a (non-callable) class attribute. Consider this equivalent definition:

def status_getter(self):
    pass

def status_setter(self, value):
    pass

class Component(metaclass=abc.ABCMeta):
    # status = property(...)
    # status.__isabstractmethod__ = True
    status = abstractmethod(property(status_getter, status_setter))

Inheriting a property is quite different from inheriting a method. You are basically replacing the property, because your class itself does not have a reference to either the getter or the setter. Despite the name, abstractmethod does not actually make the property a method; it really does nothing more than add an attribute to whatever it is applied to and return the original value.

So, to ensure that a subclass provides a read/write property, what are you to do? Skip the decorator syntax, define the getter and setter as explicit abstract methods, then define the property explicitly in terms of those private methods.

class Component(metaclass=abc.ABCMeta):
    @abstractmethod
    def _get_status(self):
        pass

    @abstractmethod
    def _set_status(self, v):
        pass

    status = property(lambda self: self._get_status(), lambda self, v: self._set_status(self, v))

Or, you can make use of __init_subclass__ (which postdates abc; its purpose is to allow class initialization that is otherwise only possible via a metaclass).

class Component:
    def __init_subclass(cls, **kwargs):
        super().__init_subclass__(**kwargs)

        try:
            p = cls.status
        except AttributeError:
            raise ValueError("Class does not define 'status' attribute")

        if not isinstance(p, property):
            raise ValueError("'status' is not a property")

        if p.fget is None:
            raise ValueError("'status' has no getter")

        if p.fset is None:
            raise ValueError("'status' has no setter")

This is actually an improvement over abc, in my opinion. If a subclass fails to define a read/write status property, an exception will be raised when the class is defined, not just when you attempt to instantiate the class.

like image 154
chepner Avatar answered Aug 18 '26 00:08

chepner



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!