Recipe20.2.Coding Properties as Nested Functions


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):     return property(**c( ))

With this little helper at hand, you can replace the explicit assignment of the property to the attribute name with the decorator syntax:

    @nested_property     def area( ):         doc = "Area of the rectangle"         def fget(self):     the area function remains the same

In Python 2.4, having a decorator line @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( ):     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( )

As you see, this alternative idiom enables us to give different names 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.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net