aboutsummaryrefslogtreecommitdiffstats
path: root/libbe/properties.py
diff options
context:
space:
mode:
authorW. Trevor King <wking@drexel.edu>2009-07-21 12:07:27 -0400
committerW. Trevor King <wking@drexel.edu>2009-07-21 12:07:27 -0400
commit795b15c60fd43e5d393f53926d679fb29c609359 (patch)
tree582700bef457eaf8386bdec77d78a1b10a8e225e /libbe/properties.py
parent680b3a15076d24d9e1ba5cd96623081d74dbd441 (diff)
downloadbugseverywhere-795b15c60fd43e5d393f53926d679fb29c609359.tar.gz
Fixed extra change-hook save in testChangeHookMutableProperty.
The actual fix was @@ -339,7 +355,10 @@ fset = funcs.get("fset") name = funcs.get("name", "<unknown>") def _fget(self, new_value=None, from_fset=False): # only used if mutable == True - value = fget(self) + if from_fset == True: + value = new_value # compare new value with cached + else: + value = fget(self) # compare current value with cached if _cmp_cached_mutable_property(self, "change hook property", name, value) != 0: # there has been a change, cache new value old_value = _get_cached_mutable_property(self, "change hook property", name) The reason for the double-save was: >>> print t.settings["List-type"]==EMPTY True (the cached value here is EMPTY) >>> t.list_type = [] (old fget compares cached EMPTY to current EMPTY, no change, so no cache. fset notices change and saves EMPTY->[]) >>> t.list_type.append(5) (now fget notices the change EMPTY->[], caches [], and calls extra save) The new way: >>> print t.settings["List-type"]==EMPTY True (the cached value here is EMPTY) >>> t.list_type = [] (fget compares cached EMPTY to new [] and saves EMPTY->[]) >>> t.list_type.append(5) (fget sees no change ([]->[]), which is correct) In addition to the fix and the related corrections to testChangeHookMutableProperty, I added details about mutables to all relevant docstrings and stripped trailing whitespace from both files.
Diffstat (limited to 'libbe/properties.py')
-rw-r--r--libbe/properties.py45
1 files changed, 32 insertions, 13 deletions
diff --git a/libbe/properties.py b/libbe/properties.py
index 8c039b2..02504e0 100644
--- a/libbe/properties.py
+++ b/libbe/properties.py
@@ -52,7 +52,7 @@ def Property(funcs):
args["fset"] = funcs.get("fset", None)
args["fdel"] = funcs.get("fdel", None)
args["doc"] = funcs.get("doc", None)
-
+
#print "Creating a property with"
#for key, val in args.items(): print key, value
return property(**args)
@@ -77,6 +77,9 @@ def local_property(name, null=None, mutable_null=False):
Define get/set access to per-parent-instance local storage. Uses
._<name>_value to store the value for a particular owner instance.
If the ._<name>_value attribute does not exist, returns null.
+
+ If mutable_null == True, we only release deepcopies of the null to
+ the outside world.
"""
def decorator(funcs):
if hasattr(funcs, "__call__"):
@@ -166,11 +169,16 @@ def _cmp_cached_mutable_property(self, cacher_name, property_name, value):
def defaulting_property(default=None, null=None,
- default_mutable=False,
- null_mutable=False):
+ mutable_default=False):
"""
Define a default value for get access to a property.
If the stored value is null, then default is returned.
+
+ If mutable_default == True, we only release deepcopies of the
+ default to the outside world.
+
+ null should never escape to the outside world, so don't worry
+ about it being a mutable.
"""
def decorator(funcs):
if hasattr(funcs, "__call__"):
@@ -181,17 +189,14 @@ def defaulting_property(default=None, null=None,
def _fget(self):
value = fget(self)
if value == null:
- if default_mutable == True:
+ if mutable_default == True:
return copy.deepcopy(default)
else:
return default
return value
def _fset(self, value):
if value == default:
- if null_mutable == True:
- value = copy.deepcopy(null)
- else:
- value = null
+ value = null
fset(self, value)
funcs["fget"] = _fget
funcs["fset"] = _fset
@@ -261,7 +266,7 @@ def cached_property(generator, initVal=None, mutable=False):
If the input value is no longer initVal (e.g. a value has been
loaded from disk or set with fset), that value overrides any
cached value, and this property has no effect.
-
+
When the cache flag is False and the stored value is initVal, the
generator is not cached, but is called on every fget.
@@ -270,7 +275,7 @@ def cached_property(generator, initVal=None, mutable=False):
In the case that mutable == True, all caching is disabled and the
generator is called whenever the cached value would otherwise be
- used. This avoids uncertainties in the value of stored mutables.
+ used.
"""
def decorator(funcs):
if hasattr(funcs, "__call__"):
@@ -331,6 +336,17 @@ def change_hook_property(hook, mutable=False):
called _after_ the new value has been stored, allowing you to
change the stored value if you want.
+ In the case of mutables, things are slightly trickier. Because
+ the property-owning class has no way of knowing when the value
+ changes. We work around this by caching a private deepcopy of the
+ mutable value, and checking for changes whenever the property is
+ set (obviously) or retrieved (to check for external changes). So
+ long as you're conscientious about accessing the property after
+ making external modifications, mutability woln't be a problem.
+ t.x.append(5) # external modification
+ t.x # dummy access notices change and triggers hook
+ See testChangeHookMutableProperty for an example of the expected
+ behavior.
"""
def decorator(funcs):
if hasattr(funcs, "__call__"):
@@ -339,7 +355,10 @@ def change_hook_property(hook, mutable=False):
fset = funcs.get("fset")
name = funcs.get("name", "<unknown>")
def _fget(self, new_value=None, from_fset=False): # only used if mutable == True
- value = fget(self)
+ if from_fset == True:
+ value = new_value # compare new value with cached
+ else:
+ value = fget(self) # compare current value with cached
if _cmp_cached_mutable_property(self, "change hook property", name, value) != 0:
# there has been a change, cache new value
old_value = _get_cached_mutable_property(self, "change hook property", name)
@@ -362,7 +381,7 @@ def change_hook_property(hook, mutable=False):
funcs["fset"] = _fset
return funcs
return decorator
-
+
class DecoratorTests(unittest.TestCase):
def testLocalDoc(self):
@@ -406,7 +425,7 @@ class DecoratorTests(unittest.TestCase):
@local_property(name="DEFAULT", null=5)
def x(): return {}
t = Test()
- self.failUnless(t.x == 5, str(t.x))
+ self.failUnless(t.x == 5, str(t.x))
t.x = 'x'
self.failUnless(t.x == 'y', str(t.x))
t.x = 'y'