How do I correctly code an abstract class and its subclasses to have getter/setter behavior?

Question:

In Python (3.11) I have this abstract class:

from abc import ABCMeta, abstractmethod
from copy import deepcopy

class BookPrototype(metaclass=ABCMeta):
  
    @property
    def title(self):
        pass

    @title.setter
    @abstractmethod
    def title(self, val):
        pass

    @abstractmethod
    def clone(self):
        pass

I create this subclass:

class ScienceFiction(BookPrototype):

    def title(self, val):
        print("this never gets called without decorator")

    def __init__(self):
        pass

    def clone(self):
        return deepcopy(self)

And use it this way:

science1 = ScienceFiction()
science1.title = "From out of nowhere"
science2 = science1.clone()
print(science2.title)
science2.title = "New title"
print(science2.title)
print(science1.title)

This code does exactly what I want, that is it creates an instance of ScienceFiction class, it clones it, it prints the title of the cloned object and again the title of the first one. So, my prints here are "From out of nowhere", "New Title", "From out of nowhere".

Problem is when, following the docs, I add the @BookPrototype.title.setter decorator to the title setter, this way:

@BookPrototype.title.setter
    def title(self, val):
        print("this gets called now")
    

In this case the print inside the title method works, BUT I can’t assign any value, so that the code prints three None.

What am doing wrong? How do I correctly code an abstract class and its subclasses to have getter/setter behavior?

Asked By: Life after Guest

||

Answers:

The title value should be stored in a variable. For instance it could be stored in self._title in the base class.
I think what you are looking for is something like:
from abc import ABCMeta, abstractmethod
from copy import deepcopy

class BookPrototype(metaclass=ABCMeta):

    def __init__(self):
        self._title: str = ""

    @property
    def title(self):
        return self._title

    @title.setter
    @abstractmethod
    def title(self, val):
        ...

    @abstractmethod
    def clone(self):
        ...

class ScienceFiction(BookPrototype):
    @BookPrototype.title.setter
    def title(self, val):
        self._title = val

    def clone(self):
        return deepcopy(self)

science1 = ScienceFiction()
science1.title = "From out of nowhere"
science2 = science1.clone()
print(science2.title)
science2.title = "New title"
print(science2.title)
print(science1.title)

# From out of nowhere
# New title
# From out of nowhere

When you remove the decorator @BookPrototype.title.setter as in :

class ScienceFiction(BookPrototype):

    def title(self, val):
        print("this never gets called without decorator")

    def __init__(self):
        pass

    def clone(self):
        return deepcopy(self)

setting the variable title with a string just override the method with a str object. This can be seen with the following :

science = ScienceFiction()
print(type(getattr(science, 'title')))
science.title = "From out of nowhere"
print(type(getattr(science, 'title')))

# <class 'method'>
# <class 'str'>  

Note that in python you can dynamically add / modify / delete attributes.

Answered By: Remi Cuingnet
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.