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.