Recipe 20.5. Using One Method as Accessorfor Multiple AttributesCredit: Raymond Hettinger ProblemPython'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. SolutionWe 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) DiscussionHere is a simple example of using this CommonProperty custom descriptor: class Rectangle(object): 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) The idea of this Rectangle class is that attributes 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 AlsoThe Library Reference and Python in a Nutshell documentation for built-ins getattr, setattr, delattr, and property. |