Welcome, guest | Sign In | My Account | Store | Cart

This recipe provides a LateBindingProperty callable which allows the getter and setter methods associated with the property to be overridden in subclasses.

Python, 39 lines
 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>

3 comments

Tim Delaney 16 years, 8 months ago  # | flag

Another approach - pass the actual methods, and extract the names from them.

def update_meta (self, other):
    self.__name__ = other.__name__
    self.__doc__ = other.__doc__
    self.__dict__.update(other.__dict__)
    return self

class LateBindingProperty (property):

    def __new__(cls, fget=None, fset=None, fdel=None, doc=None):

        if fget is not None:
            def __get__(obj, objtype=None, name=fget.__name__):
                fget = getattr(obj, name)
                return fget()

            fget = update_meta(__get__, fget)

        if fset is not None:
            def __set__(obj, value, name=fset.__name__):
                fset = getattr(obj, name)
                return fset(value)

            fset = update_meta(__set__, fset)

        if fdel is not None:
            def __delete__(obj, name=fdel.__name__):
                fdel = getattr(obj, name)
                return fdel()

            fdel = update_meta(__delete__, fdel)

        return property(fget, fset, fdel, doc)

class C(object):

    def getx(self):
        print 'C.getx'
        return self._x

    def setx(self, x):
        print 'C.setx'
        self._x = x

    def delx(self):
        print 'C.delx'
        del self._x

    x = LateBindingProperty(getx, setx, delx)

class D(C):

    def setx(self, x):
        print 'D.setx'
        super(D, self).setx(x)

    def delx(self):
        print 'D.delx'
        super(D, self).delx()

c = C()
c.x = 1
c.x
c.x
del c.x

print

d = D()
d.x = 1
d.x
d.x
del d.x

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.

Steven Bethard (author) 16 years, 8 months ago  # | flag

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

Tim Delaney 16 years, 8 months ago  # | flag

NotImplementedError. The simplest way to deal with this is to have the base class implement the property function to just raise NotImplementedError.

Created by Steven Bethard on Thu, 31 Mar 2005 (PSF)
Python recipes (4591)
Steven Bethard's recipes (7)

Required Modules

  • (none specified)

Other Information and Tasks