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?
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.