This recipe provides a LateBindingProperty callable which allows the getter and setter methods associated with the property to be overridden in subclasses.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | class LateBindingProperty(object):
def __init__(self, getname=None, setname=None, delname=None,
doc=None):
self.getname = getname
self.setname = setname
self.delname = delname
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.getname is None:
raise AttributeError('unreadable attribute')
try:
fget = getattr(obj, self.getname)
except AttributeError:
raise TypeError('%s object does not have a %s method' %
(type(obj).__name__, self.getname))
return fget()
def __set__(self, obj, value):
if self.setname is None:
raise AttributeError("can't set attribute")
try:
fset = getattr(obj, self.setname)
except AttributeError:
raise TypeError('%s object does not have a %s method' %
(type(obj).__name__, self.setname))
fset(value)
def __delete__(self, obj):
if self.delname is None:
raise AttributeError("can't delete attribute")
try:
fdel = getattr(obj, self.delname)
except AttributeError:
raise TypeError('%s object does not have a %s method' %
(type(obj).__name__, self.delname))
fdel()
|
Sometimes you want the functions that compose a property to be modifiable by subclasses. Using the property builtin, this is not directly supported because the property binds immediately to the function passed to it. So defining another function with the same name in a subclass has no effect:<pre> py> class C(object): ... def getx(self): ... return 'C' ... x = property(getx) ... py> class D(C): ... def getx(self): ... return 'D' ... py> D().x 'C' </pre>This can be worked around by using helper methods to provide late-binding to the method, e.g.:<pre> py> class C(object): ... def getx(self): ... return 'C' ... def _getx(self): ... return self.getx() ... x = property(_getx) ... py> class D(C): ... def getx(self): ... return 'D' ... py> D().x 'D' </pre>but the redundancy has something of a bad code smell. This recipe provides a simple workaround to this problem: rewrite the property descriptor to only look up the function when the property's __get__, __set__ or __del__ is invoked. This allows subclasses to override property getters and setters by simply overriding the appropriate methods:<pre> py> class C(object): ... def getx(self): ... print 'C.getx' ... return self._x ... def setx(self, x): ... print 'C.setx' ... self._x = x ... x = LateBindingProperty('getx', 'setx') ... py> c = C() py> c.x = 1 C.setx py> c.x C.getx 1 py> class D(C): ... def setx(self, x): ... print 'D.setx' ... super(D, self).setx(x) ... py> d = D() py> d.x = 1 D.setx C.setx py> d.x C.getx 1 </pre>
Another approach - pass the actual methods, and extract the names from them.
This has the advantages that:
a. You get back an actual property object (with attendant memory savings, performance increases, etc);
b. It's the same syntax as using property(fget, fset, fdel, doc) except for the name;
c. It will fail earlier (when you define the class as opposed to when you use it).
d. It's shorter ;)
e. If you inspect the property you will get back functions with the correct __name__, __doc__, etc.
Reasonable enough. The one disadvantage is that you can't specify that a subclass may implement one of the methods even though the superclass hasn't. Not sure how often this is actually desirable though...
NotImplementedError. The simplest way to deal with this is to have the base class implement the property function to just raise NotImplementedError.