This recipe refines an older recipe on creating class properties ( http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/205183). The refinement consists of: - Using a decorator (introduced in python 2.4) to "declare" a function in class scope as property. - Using a trace function to capture the locals() of the decorated function, instead of requiring the latter to return locals(), as in the older recipe.
| Python |
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | def test():
from math import radians, degrees, pi
class Angle(object):
def __init__(self,rad):
self._rad = rad
@Property
def rad():
'''The angle in radians'''
def fget(self):
return self._rad
def fset(self,angle):
if isinstance(angle,Angle): angle = angle.rad
self._rad = float(angle)
@Property
def deg():
'''The angle in degrees'''
def fget(self):
return degrees(self._rad)
def fset(self,angle):
if isinstance(angle,Angle): angle = angle.deg
self._rad = radians(angle)
def almostEquals(x,y):
return abs(x-y) < 1e-9
a = Angle(pi/3)
assert a.rad == pi/3 and almostEquals(a.deg, 60)
a.rad = pi/4
assert a.rad == pi/4 and almostEquals(a.deg, 45)
a.deg = 30
assert a.rad == pi/6 and almostEquals(a.deg, 30)
print Angle.rad.__doc__
print Angle.deg.__doc__
def Property(function):
keys = 'fget', 'fset', 'fdel'
func_locals = {'doc':function.__doc__}
def probeFunc(frame, event, arg):
if event == 'return':
locals = frame.f_locals
func_locals.update(dict((k,locals.get(k)) for k in keys))
sys.settrace(None)
return probeFunc
sys.settrace(probeFunc)
function()
return property(**func_locals)
if __name__ == '__main__':
test()
|
Discussion
As in the original recipe, defining a property involves the definition of nested functions for one or more of fget,fset,fdel. The decorator probes the decorated function, captures its locals() just before it returns and looks for the names "fget", "fset" and "fdel". The found values, along with the function's docstring are passed to property() and the resulting property is bounded to the decorated function's name.


Comments
class/metaclass. I think a metaclass is more useful and easier to read (in use, not necessarily in implementation) for this kind of thing. I've posted an example of this in my repository: http://svn.colorstudy.com/home/ianb/recipes/class_property.py <p>
I'll copy the actual code here:
A brief discussion: this is backward compatible, because property.__new__ produces a normal property instance when you call it (__new__ keeps property() from returning an instance of itself, instead returning an instances of the real property class). The metaclass causes subclasses of this custom property to return property instances again, instead of real subclasses. (There's a special case that keeps the property class itself from returning an property instances -- bases == (object,)). Though the use of "class" is unfortunate, I think this is otherwise an ideal syntax for creating properties. I wrote this code, but I know I've seen similar implementations elsewhere, so I can't claim it's my own novel idea. Ultimately you use it like:
The two approaches (decorator vs metaclass) are pretty similar in usage, although completely different in implementation. The main difference in usage is the property "signature":
with decorator versus
with metaclass. None is IMO as good as a special property syntax or code blocks would allow, e.g. something like
I find decorators are closer though by being more explicit; the class declaration would be misleading to anyone not familiar with the mutated property().
An advantage of the metaclass solution is the backwards compatibility with the builtin property(); with the decorator, a new name ("Property") has to be defined.
The two approaches (decorator vs metaclass) are pretty similar in usage, although completely different in implementation. The main difference in usage is the property "signature":
with decorator versus
with metaclass. None is IMO as good as a special property syntax or code blocks would allow, e.g. something like
I find decorators are closer though by being more explicit; the class declaration would be misleading to anyone not familiar with the mutated property().
An advantage of the metaclass solution is the backwards compatibility with the builtin property(); with the decorator, a new name ("Property") has to be defined.
Alternate without a new decorator. Here's a way that doesn't require any new decorators:
backward compatible change to property. Your improvement on my recipe is great. Thanks.
I just wanted to suggest that it's possible to change the original property to take on this decorating behaviour in a backwards compatible way:
Apply is deprecated, and sys.settrace should not be abused this way. The apply function is deprecated. This is an abuse of sys.settrace, which should only be used for debuggers, profilers, and code coverage tools. (Also sys.settrace is implementation dependent.)
Abandoning both, you can accomplish something easier than the apply technique:
Check out my recipe:
Easy Property Creation in Python
http://code.activestate.com/recipes/576742/
Sign in to comment