Recipe 20.2. Coding Properties as Nested Functions
Credit: Sean Ross, David Niergarth, Holger Krekel
Problem
You want to code properties without cluttering up your class
namespace with accessor methods that are not called directly.
Solution
Functions nested within another function are quite handy for this
task:
import math
class Rectangle(object):
def _ _init_ _(self, x, y):
self.y = x
self.y = y
def area( ):
doc = "Area of the rectangle"
def fget(self):
return self.x * self.y
def fset(self, value):
ratio = math.sqrt((1.0*value)/self.area)
self.x *= ratio
self.y *= ratio
return locals( )
area = property(**area( ))
Discussion
The standard idiom used to create a property starts with defining in
the class body several accessor methods (e.g., getter, setter,
deleter), often with boilerplate-like method names such as
setThis, getThat, or
delTheother. More often than not, such accessors are
not required except inside the property itself; sometimes (rarely)
programmers even remember to del them to clean up
the class namespace after building the property
instance.The idiom suggested in this recipe avoids cluttering up the class
namespace at all. Just write in the class body a function with the
same name you intend to give to the property. Inside that function,
define appropriate nested functions, which must
be named exactly fget, fset,
fdel, and assign an appropriate docstring named
doc. Have the outer function return a dictionary
whose entries have exactly those names, and no others: returning the
locals( ) dictionary will work, as long as your
outer function has no other local variables at that point. If you do
have other names in addition to the fixed ones, you might want to
code your return statement, for example, as:
return sub_dict(locals( ), 'doc fget fset fdel'.split( ))using the sub_dict function shown in Recipe 4.13. Any other way to subset a
dictionary will work just as well.Finally, the call to property uses the
** notation to expand a mapping into named
arguments, and the assignment rebinds the name to the resulting
property instance, so that the class namespace is left pristine.As you can see from the example in this recipe's
Solution, you don't have to define
all of the four key names: you may, and should,
omit some of them if a particular property forbids the corresponding
operation. In particular, the area function in the
solution does not define fdel because the
resulting area attribute must be not deletable.In Python 2.4, you can define a simple custom decorator to make this
recipe's suggested idiom even spiffier:
def nested_property(c):With this little helper at hand, you can replace the explicit
return property(**c( ))
assignment of the property to the attribute name with the decorator
syntax:
@nested_propertyIn Python 2.4, having a decorator line
def area( ):
doc = "Area of the rectangle"
def fget(self):
the area function remains the same
@deco right before a
def name statement is equivalent to having, right
after the def statement's body,
an assignment name =
deco(name). A mere difference of syntax
sugar, but it's useful: anybody reading the source
code of the class knows up front that the function or method
you're def'ing
is meant to get decorated in a certain way, not to get used exactly
as coded. With the Python 2.3 syntax, somebody reading in haste might
possibly miss the assignment statement that comes
after the def.Returning locals works only if your outer function
has no other local variables besides fget,
fset, fdel, and
doc. An alternative idiom to avoid this
restriction is to move the call to property
inside the outer function:
def area( ):As you see, this alternative idiom enables us to give different names
what_is_area = "Area of the rectangle"
def compute_area(self):
return self.x * self.y
def scale_both_sides(self, value):
ratio = math.sqrt((1.0*value)/self.area)
self.x *= ratio
self.y *= ratio
return property(compute_area, scale_both_sides, None, what_is_area)
area = area( )
to the getter and setter accessors, which is not a big deal because,
as mentioned previously, accessors are often named in uninformative
ways such as getThis and setThat
anyway. But, if your opinion differs, you may prefer this idiom, or
its slight variant based on having the outer function return a
tuple of values for
property's argument rather than a
dict. In other words, the variant obtained by
changing the last two statements of this latest snippet to:
return compute_area, scale_both_sides, None, what_is_area
area = property(*area( ))
See Also
Library Reference and Python in a
Nutshell docs on built-in functions
property and locals.