def set_default_attrs( obj, **kws ): # e if this was general, we could refile into py_utils, but it turns out it's not general enough """For each attr=val pair in **kws, if attr is not set in obj, set it, without doing usage or change tracking. If obj supports __setattr_default__ (not a special name to Python itself), use that, otherwise use hasattr and setattr (with a temporary dynamic suppression of usage or change tracking, if possible -- nim for now except as an assertion). Note: this is not very efficient (or self-contained) for general use, for which pure hasattr/setattr would be sufficient. """ # Note about why we disable tracking: hasattr itself does legitimately track use of the attr, # since testing thereness does use its "value" in a general sense (as if value was _UNSET_ when not there). # But, in this specific code, we don't want client (caller) to see us using attr, # since from client POV, all we're doing is changing _UNSET_ to v, which are equivalent values # (as client is declaring by calling us). # So we need to discard tracked usage of attr here. # We also need to discard any change from the set to default value, # except that we can probably ignore that issue for now (due to an accident of how current code uses this), # since if the attr is unset, it was never yet used, so invalidating it should have no effect. if 1: from exprs.Exprs import is_pure_expr if is_pure_expr(obj): assert 0, ( "set_default_attrs called on pure_expr %r is almost surely a bug; normally do it in _init_instance" % (obj,) ) import foundation.changes as changes try: method = obj.__setattr_default__ except AttributeError: ## print "fyi: set_default_attrs using general case for",obj,kws.keys() # this happens for all non-tracked StatePlaces # general case - let's hope the object doesn't have tracked attrs; the temporary assertion here might not catch this ###e instead, we should actively suppress usage/change tracking, so we can permit it in obj # (tho careless uses of that would lead to bugs) mc = changes.begin_disallowing_usage_tracking("set_default_attrs (hasattr/setattr) for %r" % obj) # note: the argument is just an explanation for use in error messages ##e OPTIM: don't precompute that arg try: for k, v in kws.iteritems(): if not hasattr(obj, k): setattr(obj, k, v) finally: changes.end_disallowing_usage_tracking(mc) else: # use the special method mc = changes.begin_disallowing_usage_tracking("set_default_attrs (__setattr_default__) for %r" % obj) # e optim: remove this check; it only catches usage tracking, not change tracking (ie inval of something by this), anyway try: for k, v in kws.iteritems(): method( k, v ) # does its own usage/change-tracking suppression (we assume); this is partially checked (for now) finally: changes.end_disallowing_usage_tracking(mc) return # from set_default_attrs
def set_default_attrs( obj, **kws ): #e if this was general, we could refile into py_utils, but it turns out it's not general enough """For each attr=val pair in **kws, if attr is not set in obj, set it, without doing usage or change tracking. If obj supports __setattr_default__ (not a special name to Python itself), use that, otherwise use hasattr and setattr (with a temporary dynamic suppression of usage or change tracking, if possible -- nim for now except as an assertion). Note: this is not very efficient (or self-contained) for general use, for which pure hasattr/setattr would be sufficient. """ # Note about why we disable tracking: hasattr itself does legitimately track use of the attr, # since testing thereness does use its "value" in a general sense (as if value was _UNSET_ when not there). # But, in this specific code, we don't want client (caller) to see us using attr, # since from client POV, all we're doing is changing _UNSET_ to v, which are equivalent values # (as client is declaring by calling us). # So we need to discard tracked usage of attr here. # We also need to discard any change from the set to default value, # except that we can probably ignore that issue for now (due to an accident of how current code uses this), # since if the attr is unset, it was never yet used, so invalidating it should have no effect. if 1: from exprs.Exprs import is_pure_expr if is_pure_expr(obj): assert 0, "set_default_attrs called on pure_expr %r is almost surely a bug; normally do it in _init_instance" % ( obj, ) import foundation.changes as changes try: method = obj.__setattr_default__ except AttributeError: ## print "fyi: set_default_attrs using general case for",obj,kws.keys() # this happens for all non-tracked StatePlaces # general case - let's hope the object doesn't have tracked attrs; the temporary assertion here might not catch this ###e instead, we should actively suppress usage/change tracking, so we can permit it in obj # (tho careless uses of that would lead to bugs) mc = changes.begin_disallowing_usage_tracking( 'set_default_attrs (hasattr/setattr) for %r' % obj) # note: the argument is just an explanation for use in error messages ##e OPTIM: don't precompute that arg try: for k, v in kws.iteritems(): if not hasattr(obj, k): setattr(obj, k, v) finally: changes.end_disallowing_usage_tracking(mc) else: # use the special method mc = changes.begin_disallowing_usage_tracking( 'set_default_attrs (__setattr_default__) for %r' % obj) #e optim: remove this check; it only catches usage tracking, not change tracking (ie inval of something by this), anyway try: for k, v in kws.iteritems(): method( k, v ) # does its own usage/change-tracking suppression (we assume); this is partially checked (for now) finally: changes.end_disallowing_usage_tracking(mc) return # from set_default_attrs
def __getitem__(self, key): try: return dict.__getitem__(self, key) except KeyError: import foundation.changes as changes mc = changes.begin_disallowing_usage_tracking(self) # note: argument is just explanation for use in error messages try: val = self._way(key) # note, it needs to be legal for something during this to reallow using tracking for itself, locally except: # try not to hurt this object for use at other keys, but good to reraise exception to whoever accessed this key # (maybe something's wrong with this key, but good even if not, i think [061121]) # Note: the exception might be an error being reported by begin_disallowing_usage_tracking! # So we should do the ending of that disallowing -- the initial raising of that exception should not do it. changes.end_disallowing_usage_tracking(mc) raise else: changes.end_disallowing_usage_tracking(mc) dict.__setitem__(self, key, val) return val pass
def _set_defaultValue(self, defaultValue): """If attr is not set, set it to defaultValue; never do any usage or change tracking. (WARNING: This will cause bugs unless all providers of access to a given attribute-instance use this consistently (same default value, and all of them using this rather than some of them providing access to the attr with no default value); they must all use it before providing access to that attr to any client object.) """ # see comments in set_default_attrs about why we need this to do no usage or change tracking if self.valid: return # easy case # Dilemma: we might have no value, or we might have one computable by an initial-value compute method... # or we might have such a method which will raise LvalError_ValueIsUnset when we try it # (e.g. valfunc method in some client code). The only way to find out is to use that method, # but what if doing so tracked some usage? I think it's normal for that to happen... # OTOH, it's not correct, I think, to have two inconsistent ways of setting a default value -- # code that calls this, vs. code that supplies an initial-value compute method that actually works. # So, maybe we should be able to ask the method which kind it is... but for now, assume it could # be either. If it computes a real value, we better not run it now, since usage would get tracked... # though that's an error... one way to detect is to return now and let it run, but that led to infrecur # in self.delegate in If.cond (don't know why). But setting defaultValue now risks hiding bugs. # That leaves: run it now, asserting no usage tracking. [Note: current calling code redundantly asserts the same thing. #e] # # One case which is not covered: something asked if we were set, was told "we're unset", and tracked its usage of us. # Ideally we'd assert right here that we have no current inval-subscribers. ####e DOIT # (For now, if we *do* have subscribers who learned we were unset, and if we leave them untouched, # it's indeed a bug that they asked that before we were initialized, but it's not so bad that they'll # get invalled when something changes us later.) printnim("assert no current inval-subscribers") mc = changes.begin_disallowing_usage_tracking('_set_defaultValue in %r' % self) # note: the argument is just an explanation for use in error messages ##e OPTIM: don't precompute that arg try: try: val = self._compute_value() # don't call get_value since it calls track_use except LvalError_ValueIsUnset: # this is the only non-error case! # (the following just inlines self.set_constant_value(defaultValue):) self._value = defaultValue self.valid = True ## print "_set_defaultValue returning after set" return except: # any other exception, including discovering usage tracking in that computation [once that works when it happens], # should be reported, but then (I think) needn't prevent this from working. print_compact_traceback("error: exception (ignored) in _set_defaultValue trying initval-computation in %r: " % self) self._value = defaultValue self.valid = True return else: # If a value was computed, that conflicts with using this method... report that error (even if the values agree), # but which value should we use? Let's use the computed one for now. # NOTE: In present code [061121], this can also happen if usage tracking occurred, since it's not detected # until the end_disallowing_usage_tracking call below (since begin_disallowing_usage_tracking is not fully # implemented). print "error: %r computed initial value %r (using it) but was also given an explicit _set_defaultValue(%r)" % \ (self, val, defaultValue) self._value = val self.valid = True return pass finally: changes.end_disallowing_usage_tracking(mc) pass # end of method _set_defaultValue
def _set_defaultValue(self, defaultValue): """If attr is not set, set it to defaultValue; never do any usage or change tracking. (WARNING: This will cause bugs unless all providers of access to a given attribute-instance use this consistently (same default value, and all of them using this rather than some of them providing access to the attr with no default value); they must all use it before providing access to that attr to any client object.) """ # see comments in set_default_attrs about why we need this to do no usage or change tracking if self.valid: return # easy case # Dilemma: we might have no value, or we might have one computable by an initial-value compute method... # or we might have such a method which will raise LvalError_ValueIsUnset when we try it # (e.g. valfunc method in some client code). The only way to find out is to use that method, # but what if doing so tracked some usage? I think it's normal for that to happen... # OTOH, it's not correct, I think, to have two inconsistent ways of setting a default value -- # code that calls this, vs. code that supplies an initial-value compute method that actually works. # So, maybe we should be able to ask the method which kind it is... but for now, assume it could # be either. If it computes a real value, we better not run it now, since usage would get tracked... # though that's an error... one way to detect is to return now and let it run, but that led to infrecur # in self.delegate in If.cond (don't know why). But setting defaultValue now risks hiding bugs. # That leaves: run it now, asserting no usage tracking. [Note: current calling code redundantly asserts the same thing. #e] # # One case which is not covered: something asked if we were set, was told "we're unset", and tracked its usage of us. # Ideally we'd assert right here that we have no current inval-subscribers. ####e DOIT # (For now, if we *do* have subscribers who learned we were unset, and if we leave them untouched, # it's indeed a bug that they asked that before we were initialized, but it's not so bad that they'll # get invalled when something changes us later.) printnim("assert no current inval-subscribers") mc = changes.begin_disallowing_usage_tracking( '_set_defaultValue in %r' % self) # note: the argument is just an explanation for use in error messages ##e OPTIM: don't precompute that arg try: try: val = self._compute_value( ) # don't call get_value since it calls track_use except LvalError_ValueIsUnset: # this is the only non-error case! # (the following just inlines self.set_constant_value(defaultValue):) self._value = defaultValue self.valid = True ## print "_set_defaultValue returning after set" return except: # any other exception, including discovering usage tracking in that computation [once that works when it happens], # should be reported, but then (I think) needn't prevent this from working. print_compact_traceback( "error: exception (ignored) in _set_defaultValue trying initval-computation in %r: " % self) self._value = defaultValue self.valid = True return else: # If a value was computed, that conflicts with using this method... report that error (even if the values agree), # but which value should we use? Let's use the computed one for now. # NOTE: In present code [061121], this can also happen if usage tracking occurred, since it's not detected # until the end_disallowing_usage_tracking call below (since begin_disallowing_usage_tracking is not fully # implemented). print "error: %r computed initial value %r (using it) but was also given an explicit _set_defaultValue(%r)" % \ (self, val, defaultValue) self._value = val self.valid = True return pass finally: changes.end_disallowing_usage_tracking(mc) pass # end of method _set_defaultValue