From ffd21689bedd8c3ddb6a2b4ef42beb062c75aae6 Mon Sep 17 00:00:00 2001 From: wirawan Date: Fri, 28 May 2010 18:47:56 +0000 Subject: [PATCH] * Parameters class: a way to look for parameters in definite search order across functions, so that these parameters do not have to be passed in kitchen-sink manner. WARNING: STILL UNDER TESTING. --- sugar.py | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 1 deletion(-) diff --git a/sugar.py b/sugar.py index 7963b6d..4488b85 100644 --- a/sugar.py +++ b/sugar.py @@ -1,6 +1,6 @@ #!/usr/bin/ipython -pylab # -# $Id: sugar.py,v 1.3 2010-02-19 18:42:15 wirawan Exp $ +# $Id: sugar.py,v 1.4 2010-05-28 18:47:56 wirawan Exp $ # # Created: 20100121 # Wirawan Purwanto @@ -63,3 +63,145 @@ def dict_join(*dicts): return rslt +class Parameters(object): + """A standardized way to define and/or pass parameters (with possible + default values) among routines. + This provides a very flexible lookup scheme for a parameter with a given name. + It scans through the namescopes (dicts) in a deterministic order, returning + the first one found. + This, hopefully, gets rid of kitchen-sink parameter passing, at least from + programmer's point of view. + + WARNING: This object is derived object instead of python dict, so as to avoid + messing with standard dict names. + Names reserved by this class begin and end with an underscore. + Names reserved by python begin and end with two underscores. + So, avoid specifying parameters with both leading and trailing underscores. + + Some uses: + + def stuff(params=None, **kwparams): + # `params' defines the standard way of passing parameters, which is + # via a Parameters object. + # `kwparams' determine a quick way of temporarily overriding a parameter + # value. + prm = Parameters(kwparams, params, global_defaults) + for step in prm.steps: + ... + + Reserved members: + * _no_null_ = (True/False, default False) look for non-null values in all + the parameter lists until one is found. + * _list_ = (list) the list of parameter dicts to search from. + * _prm_ = (dict) the most overriding list of parameters. + """ + def __init__(self, *_override_dicts_, **_opts_): + """ + Again, keyword arguments passed here will become the most overriding options. + """ + prm = _opts_ + self.__dict__["_no_null_"] = ifelse(_opts_.get("_no_null_"), True, False) + self.__dict__["_prm_"] = prm + paramlist = (prm,) + _override_dicts_ #+ tuple(deflist)) + self.__dict__["_list_"] = [ p for p in paramlist if p != None ] + def __getattr__(self, key): + """Allows options to be accessed in attribute-like manner, like: + opt.niter = 3 + instead of + opt['niter'] = 3 + """ + if self._no_null_: + for ov in self._list_: + if key in ov and ov[key] != None: return ov[key] + else: + for ov in self._list_: + if key in ov: return ov[key] + # Otherwise: + return object.__getattribute__(self, key) + def __setattr__(self, key, value): + """This method always sets the value on the object's dictionary. + Values set will override any values set in the input parameter lists.""" + self._prm_[key] = value + def __contains__(self, key): + if self._no_null_: + for ov in self._list_: + if key in ov and ov[key] != None: return True + else: + for ov in self._list_: + if key in ov: return True + return False + def __getitem__(self, key): + if self._no_null_: + for ov in self._list_: + if key in ov and ov[key] != None: return ov[key] + else: + for ov in self._list_: + if key in ov: return ov[key] + raise KeyError, "Cannot find parameter `%s'" % key + def __setitem__(self, key, value): + self._prm_[key] = value + # TODO in the future for iterative accesses: + # -- not that essential because we know the name of + # the parameters we want to get: + #def __iter__(self): + # return self._prm_.__iter__ + #def _iteritems_(self): + # return self._prm_.iteritems() + def _update_(self, srcdict): + self._prm_.update(srcdict) + def _create_(self, kwparams="_opts_", userparams="opts", *defaults): + """Creates a new Parameters() object for standardized function-level + parameter lookup. + This routine *must* be called by the function where we want to access these + parameters, and where some parameters are to be overriden via function arguments, + etc. + + The order of lookup is definite: + * + + class Something(object): + def __init__(self, ...): + self.opts = Parameters() + self.opts.cleanup = True # example + + def doit(self, src=None, info=None, + _defaults_=dict(src="source.txt", info="INFO.txt", debug=1), + **_opts_): + # FIXME: use self-introspection to reduce kitchen-sink params here: + p = self.opts._create_() + # ^ This will create an equivalent of: + # Parameters(locals(), _opts_, _opts_.get('opts'), self.opts, _defaults) + """ + # Look up the stack of the calling function in order to retrieve its + # local variables + from inspect import stack + caller = stack()[1][0] # one frame up; element-0 is the stack frame + + # local variables will be the first to look for + localvars = caller.f_locals + contexts = [ localvars ] + # then _opts_ excess-keyword parameters (see example of doit() above) + if kwparams in localvars: + _opts_ = localvars[kwparams] + contexts.append(_opts_) + else: + _opts_ = {} + # then opts, an explicitly-defined argument carrying set of parameters + if userparams in localvars: + opts = localvars[userparams] + contexts.append(opts) + else: + opts = {} + if userparams in _opts_: + contexts.append(_opts_[userparams]) + + # then this own Parameters data will come here: + contexts.append(self) + + # then any defaults + contexts += [ d for d in defaults ] + + # Now construct the Parameters() class for this calling function: + return Parameters(*contexts) + +