Say you have an attribute in a base class with a single setter
method that will be used in all subclasses, but with different getter
methods in all subclasses. Ideally you only want to write the code for the setter
method once in the base class. Additionally, you want to make the base class abstract because it doesn't have a complete getter
method. Is there any way to do this?
Naively, I think it should go something like this:
class _mystring(object):
__metaclass__ = abc.ABCMeta
@abc.abstractproperty
def str(self):
pass
@str.setter
def str(self,value):
self._str = value
class uppercase(_mystring):
@_mystring.str.getter
def str(self):
return self._str.upper()
But upper = uppercase()
fails with Can't instantiate abstract class uppercase with abstract methods str
.
If I change the @abc.abstractproperty
in class _mystring
to @property
, then upper = uppercase()
works, but so does upper = _mystring()
, which I don't want.
What am I missing?
Thanks!
I managed to make a small wrapper around abstractproperty
that will do it:
class partialAP(abc.abstractproperty):
def getter(self, func):
if getattr(func, '__isabstractmethod__', False) or getattr(self.fset, '__isabstractmethod__', False):
p = partialAP(func, self.fset)
else:
p = property(func, self.fset)
return p
def setter(self, func):
if getattr(self.fget, '__isabstractmethod__', False) or getattr(func, '__isabstractmethod__', False):
p = partialAP(self.fget, func)
else:
p = property(self.fset, func)
return p
Then your code works with just a slight modification:
class _mystring(object):
__metaclass__ = abc.ABCMeta
@partialAP
@abc.abstractmethod
def str(self):
pass
@str.setter
def str(self,value):
self._str = value
class uppercase(_mystring):
@_mystring.str.getter
def str(self):
return self._str.upper()
And then the behavior is as desired:
>>> _mystring()
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
_mystring()
TypeError: Can't instantiate abstract class _mystring with abstract methods str
>>> u = uppercase()
>>> u.str = 'a'
>>> u.str
'A'
My partialAP
wrapper must be used in conjuction with abc.abstractmethod
. What you do is you use abstractmethod
to decorate the "piece" of the property (getter or setter) that you want to be abstract, and use partialAP
as a second decorator. partialAP
defines getter/setter functions that replace the property with a normal one only if both getter and setter are non-abstract.
Obviously you'd have to extend this a bit to make it worth with property deleters too. There could also be corner cases that won't be handled right if you have a more complex inheritance hierarchy.
Old solution for posterity:
class _mystring(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def _strGet(self):
pass
def _strSet(self,value):
self._str = value
str = property(_strGet, _strSet)
class uppercase(_mystring):
def _strGet(self):
return self._str.upper()
str = _mystring.str.getter(_strGet)
Then it works:
>>> _mystring()
Traceback (most recent call last):
File "<pyshell#39>", line 1, in <module>
_mystring()
TypeError: Can't instantiate abstract class _mystring with abstract methods _strGet
>>> u = uppercase()
>>> u.str = 'a'
>>> u.str
'A'
The trick is that you have to not use the convenient decorators. The abstractproperty
decorator marks the entire property as abstract. What you have to do is create two methods, an abstract one (for the getter) and a regular one (for the setter), then create a regular property that combines them. Then when you extend the class, you must override the abstract getter and explicitly "mix" it with the base class property (using _mystring.str.getter
).
The reason you can't use @_mystring.str.getter
is that the decorator forces both the getter and the setter to have the same name as the property itself. If you want to mark just one of the two as abstract, they have to have different names or you can't get at them separately.
Note that this solution requires subclasses to give their getters the right name (_strGet
in this case), in order to satisfy the ABC that they have overriden the abstract method.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With