From 9ef6ad576c01444e35b91dfee8d05b39ec911d45 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 20 Jul 2009 18:39:31 -0400 Subject: Cleaned up some outdated libbe.settings_object.EMPTY cruft. From back before commit wking@drexel.edu-20090619184215-nfx205yaj02sqrqx cleaned up the versioned_property implementation. Also a few style fixes and typos. --- libbe/properties.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/properties.py') diff --git a/libbe/properties.py b/libbe/properties.py index e9affcb..8c039b2 100644 --- a/libbe/properties.py +++ b/libbe/properties.py @@ -296,7 +296,7 @@ def cached_property(generator, initVal=None, mutable=False): def primed_property(primer, initVal=None): """ - Just like a generator_property, except that instead of returning a + Just like a cached_property, except that instead of returning a new value and running fset to cache it, the primer performs some background manipulation (e.g. loads data into instance.settings) such that a _second_ pass through fget succeeds. -- cgit From 795b15c60fd43e5d393f53926d679fb29c609359 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 21 Jul 2009 12:07:27 -0400 Subject: Fixed extra change-hook save in testChangeHookMutableProperty. The actual fix was @@ -339,7 +355,10 @@ fset = funcs.get("fset") name = funcs.get("name", "") 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. --- libbe/properties.py | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) (limited to 'libbe/properties.py') 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 .__value to store the value for a particular owner instance. If the .__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", "") 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' -- cgit From 049825b147e291e11542b4c06ea7800cb0671bd6 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 21 Jul 2009 16:14:53 -0400 Subject: libbe.properties unittest changes due to "extra change-hook save" fix. Missed these earlier. --- libbe/properties.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'libbe/properties.py') diff --git a/libbe/properties.py b/libbe/properties.py index 02504e0..144220b 100644 --- a/libbe/properties.py +++ b/libbe/properties.py @@ -594,14 +594,17 @@ class DecoratorTests(unittest.TestCase): t.x = [] self.failUnless(t.old == None, t.old) self.failUnless(t.new == [], t.new) + self.failUnless(t.hook_calls == 1, t.hook_calls) a = t.x a.append(5) t.x = a self.failUnless(t.old == [], t.old) self.failUnless(t.new == [5], t.new) + self.failUnless(t.hook_calls == 2, t.hook_calls) t.x = [] self.failUnless(t.old == [5], t.old) self.failUnless(t.new == [], t.new) + self.failUnless(t.hook_calls == 3, t.hook_calls) # now append without reassigning. this doesn't trigger the # change, since we don't ever set t.x, only get it and mess # with it. It does, however, update our t.new, since t.new = @@ -609,25 +612,26 @@ class DecoratorTests(unittest.TestCase): t.x.append(5) self.failUnless(t.old == [5], t.old) self.failUnless(t.new == [5], t.new) + self.failUnless(t.hook_calls == 3, t.hook_calls) # however, the next t.x get _will_ notice the change... a = t.x self.failUnless(t.old == [], t.old) self.failUnless(t.new == [5], t.new) - self.failUnless(t.hook_calls == 6, t.hook_calls) + self.failUnless(t.hook_calls == 4, t.hook_calls) t.x.append(6) # this append(6) is not noticed yet self.failUnless(t.old == [], t.old) self.failUnless(t.new == [5,6], t.new) - self.failUnless(t.hook_calls == 6, t.hook_calls) + self.failUnless(t.hook_calls == 4, t.hook_calls) # this append(7) is not noticed, but the t.x get causes the # append(6) to be noticed t.x.append(7) self.failUnless(t.old == [5], t.old) self.failUnless(t.new == [5,6,7], t.new) - self.failUnless(t.hook_calls == 7, t.hook_calls) + self.failUnless(t.hook_calls == 5, t.hook_calls) a = t.x # now the append(7) is noticed self.failUnless(t.old == [5,6], t.old) self.failUnless(t.new == [5,6,7], t.new) - self.failUnless(t.hook_calls == 8, t.hook_calls) + self.failUnless(t.hook_calls == 6, t.hook_calls) suite = unittest.TestLoader().loadTestsFromTestCase(DecoratorTests) -- cgit