Recipe 20.5. Using One Method as Accessorfor Multiple Attributes
Credit: Raymond Hettinger
Problem
Python's built-in property
descriptor is quite handy but only as long as you want to use a
separate method as the accessor of each attribute you make into a
property. In certain cases, you prefer to use the same method to
access several different attributes, and property
does not support that mode of operation.
Solution
We need to code our own custom descriptor, which gets the attribute
name in _ _init_ _, saves it, and passes it on to
the accessors. For convenience, we also provide useful defaults for
the various accessors. You can still pass in None
explicitly if you want to forbid certain kinds of access but the
default is to allow it freely.
class CommonProperty(object):
def _ _init_ _(self, realname, fget=getattr, fset=setattr, fdel=delattr,
doc=None):
self.realname = realname
self.fget = fget
self.fset = fset
self.fdel = fdel
self._ _doc_ _ = doc or "
def _ _get_ _(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError, "can't get attribute"
return self.fget(obj, self.realname)
def _ _set_ _(self, obj, value):
if self.fset is None:
raise AttributeError, "can't set attribute"
self.fset(obj, self.realname, value)
def _ _delete_ _(self, obj):
if self.fdel is None:
raise AttributeError, "can't delete attribute"
self.fdel(obj, self.realname, value)
Discussion
Here is a simple example of using this
CommonProperty custom descriptor:
class Rectangle(object):The idea of this Rectangle class is that attributes
def _ _init_ _(self, x, y):
self._x = x # don't trigger _setSide prematurely
self.y = y # now trigger it, so area gets computed
def _setSide(self, attrname, value):
setattr(self, attrname, value)
self.area = self._x * self._y
x = CommonProperty('_x', fset=_setSide, fdel=None)
y = CommonProperty('_y', fset=_setSide, fdel=None)
x and y may be freely accessed but
never deleted; when either of these attributes is set, the
area attribute must be recomputed at once. You could
alternatively recompute the area on the fly each time
it's accessed, using a simple
property for the purpose; however, if
area is accessed often and sides are changed
rarely, the architecture of this simple example obviously can be
preferable.In this simple example of CommonProperty use, we
just need to be careful on the very first attribute setting in
_ _init_ _: if we carelessly used self.x =
x, that would trigger the call to
_setSide, which, in turn, would try to use
self._y before the _y attribute is
set.Another issue worthy of mention is that if any one or more of the
fget, fset, or
fdel arguments to CommonProperty
is defaulted, the realname argument must be
different from the attribute name to which the
CommonProperty instance is assigned; otherwise,
unbounded recursion would occur on trying the corresponding operation
(in practice, you'd get a
RecursionLimitExceeded exception).
See Also
The Library Reference and Python in
a Nutshell documentation for built-ins
getattr, setattr,
delattr, and property.