* Class Parameters: important change of behavior for more safety to avoid

potential confusion.

* Detail of the important changes:

  1) by default now the function's local variables are not included in the
     lookup chain. Use _localvars_ flag if you insist on doing that; but
     be aware of the caveat (see Parameters' documentation).

  2) the default _kwparam_ and _userparam_ names are now passed on to the
     child Parameters object instead of the override values given through
     the _create_ function argument.
     Rationale: To begin with, the default names defined during the creation
     of the parent Parameters object should be used by *all* the relevant
     functions.
     Overrides should be regarded as exceptional rather than common cases.

  3) Value overrides (_kwparam_, _userparam_, etc.) in _create_ must now
     be specified in keyword=value manner rather than as positional argument.
     Rationale: Those overrides must be made conspicuous.
     The keyword=value approach also makes the code more resilient to minor
     API changes.
     All unnamed parameters are supposed to be low-priority defaults.
master
wirawan 13 years ago
parent 4aff740f55
commit aad559d86b
  1. 81
      params/params_flat.py
  2. 43
      params/params_flat_test.py

@ -1,4 +1,4 @@
# $Id: params_flat.py,v 1.3 2011-09-07 15:05:54 wirawan Exp $ # $Id: params_flat.py,v 1.4 2011-09-09 18:58:48 wirawan Exp $
# #
# wpylib.params.params_flat module # wpylib.params.params_flat module
# Created: 20100930 # Created: 20100930
@ -124,15 +124,30 @@ class Parameters(dict):
Options: Options:
* _no_null_ = if True, look for the first non-None value. * _no_null_ = if True, look for the first non-None value.
* _flatten_ = will flatten the key-value pairs. * _flatten_ = will flatten the key-value pairs from the overriding dicts
into the self object.
Note that this may make the Parameters object unnecessarily large in memory. Note that this may make the Parameters object unnecessarily large in memory.
Additionally, this means that the updates in the contents of the dicts Additionally, this means that the updates in the contents of the dicts
passed as the _override_dicts_ can no longer be reflected in this object passed as the _override_dicts_ can no longer be reflected in this object
because of the shallow copying involved here. because of the shallow copying involved here.
* _kwparam_ At present, the `_flatten_' attribute will not be propagated to the child
* _userparam_ Parameters objects created by this parent object.
At present, the `flatten' attribute will not be propagated to the child * _kwparam_ = the name of the excess argument dict to look for in the
Parameters objects created by this parent object. function argument list (default: `_opts_').
* _userparam_ = the name of the explicitly defined user-defined parameter
(of a dict type) in the function argument list (default: `_p').
* _localvars_ = set to true to include function local variables in the
lookup chain. Default is False because it can be very confusing!
We just have no control on what local variables would be involved
in a function and the sheer potential of creating vars with the same name
as the value we want to look up---all will open up to infinite possibility
of surprises.
At present, the `_localvars_' attribute will not be propagated to the child
Parameters objects created by this parent object.
Caveat: only variables defined till the point of calling of the method
_create_() below will be searched in the lookup process.
Values defined or updated later will not be reflected in the lookup process.
(See params_flat_test.py, test2 and test2b routines.)
""" """
# Remove standard dict procedure names not beginning with "_": # Remove standard dict procedure names not beginning with "_":
@ -156,8 +171,10 @@ class Parameters(dict):
self.__dict__["_kwparam_"] = _opts_.get("_kwparam_", "_opts_") self.__dict__["_kwparam_"] = _opts_.get("_kwparam_", "_opts_")
self.__dict__["_userparam_"] = _opts_.get("_userparam_", "_p") self.__dict__["_userparam_"] = _opts_.get("_userparam_", "_p")
self.__dict__["_no_null_"] = ifelse(_opts_.get("_no_null_"), True, False) self.__dict__["_no_null_"] = ifelse(_opts_.get("_no_null_"), True, False)
self.__dict__["_localvars_"] = ifelse(_opts_.get("_localvars_"), True, False)
# Finally, filter out reserved keywords from the dict: # Finally, filter out reserved keywords from the dict:
for badkw in ("_kwparam_", "_userparam_", "_no_null_", "_flatten_"): for badkw in ("_kwparam_", "_userparam_", "_no_null_", "_flatten_", \
"_localvars_"):
#if badkw in self: del self[badkw] -- recursive!!! #if badkw in self: del self[badkw] -- recursive!!!
if dict.__contains__(self,badkw): del self[badkw] if dict.__contains__(self,badkw): del self[badkw]
def _copy_(self): def _copy_(self):
@ -240,7 +257,7 @@ class Parameters(dict):
rslt = self._copy_() rslt = self._copy_()
rslt._update_(srcdict) rslt._update_(srcdict)
return rslt return rslt
def _create_(self, kwparam=None, userparam=None, *defaults): def _create_(self, *defaults, **_options_):
"""Creates a new Parameters() object for standardized function-level """Creates a new Parameters() object for standardized function-level
parameter lookup. parameter lookup.
This routine *must* be called by the function where we want to access these This routine *must* be called by the function where we want to access these
@ -249,6 +266,7 @@ class Parameters(dict):
The order of lookup is definite: The order of lookup is definite:
* local variables of the calling subroutine will take precedence * local variables of the calling subroutine will take precedence
(if _localvars_ is set to True)
* the excess keyword-based parameters, * the excess keyword-based parameters,
* user-supplied Parameters-like object, which is * user-supplied Parameters-like object, which is
* the dicts (passed in the `defaults' unnamed parameter list) is searched * the dicts (passed in the `defaults' unnamed parameter list) is searched
@ -269,25 +287,42 @@ class Parameters(dict):
# FIXME: use self-introspection to reduce kitchen-sink params here: # FIXME: use self-introspection to reduce kitchen-sink params here:
p = self.opts._create_(_defaults_) p = self.opts._create_(_defaults_)
# ^ This will create an equivalent of: # ^ This will create an equivalent of:
# Parameters(locals(), _opts_, _opts_.get('opts'), self.opts, _defaults) # Parameters(_opts_, _opts_.get('_p'), self.opts, _defaults_)
# Now use it: # Now use it:
if p.cleanup: if p.cleanup:
... do something self.do_the_cleanup() # ... do something
* Options accepted by the _create_ function are:
- _kwparam_ (string) = name of excess-parameter dict.
Default: None; refer back to the object's _kwparam_ attribute.
- _userparam_ (string) = name of explicitly-given parameter dict
Default: None; refer back to the object's _userparam_ attribute.
- _localvars_ (boolean) = whether to include the local vars in the
lookup chain. Default: None; refer back to the object's
_localvars_ attribute.
""" """
# Look up the stack of the calling function in order to retrieve its # Look up the stack of the calling function in order to retrieve its
# local variables # local variables
from inspect import stack from inspect import stack
caller = stack()[1][0] # one frame up; element-0 is the stack frame caller = stack()[1][0] # one frame up; element-0 is the stack frame
_kwparam_ = _options_.get("_kwparam_", None)
_userparam_ = _options_.get("_userparam_", None)
_localvars_ = _options_.get("_localvars_", None)
if kwparam == None: kwparam = self._kwparam_ if _kwparam_ == None: _kwparam_ = self._kwparam_
if userparam == None: userparam = self._userparam_ if _userparam_ == None: _userparam_ = self._userparam_
if _localvars_ == None: _localvars_ = self._localvars_
# local variables will be the first scope to look for # local variables will be the first scope to look for
localvars = caller.f_locals localvars = caller.f_locals
contexts = [ localvars ] #print "?? localvars = ", _localvars_
if _localvars_:
contexts = [ localvars ]
else:
contexts = []
# then _opts_ excess-keyword parameters (see example of doit() above) # then _opts_ excess-keyword parameters (see example of doit() above)
if kwparam in localvars: if _kwparam_ in localvars:
_opts_ = localvars[kwparam] _opts_ = localvars[_kwparam_]
if _opts_ != None: if _opts_ != None:
# add this minimal check for a dict-like behavior rather # add this minimal check for a dict-like behavior rather
# than encountering a strange error later # than encountering a strange error later
@ -295,13 +330,13 @@ class Parameters(dict):
raise TypeError, \ raise TypeError, \
("The keyword parameter (variable/parameter `%s' in function `%s')" + ("The keyword parameter (variable/parameter `%s' in function `%s')" +
" is not a dict-like object)") \ " is not a dict-like object)") \
% (kwparam, caller.f_code.co_name) % (_kwparam_, caller.f_code.co_name)
contexts.append(_opts_) contexts.append(_opts_)
else: else:
_opts_ = {} _opts_ = {}
# then opts, an explicitly-defined argument which contain a set of parameters # then opts, an explicitly-defined argument which contain a set of parameters
if userparam in localvars: if _userparam_ in localvars:
opts = localvars[userparam] opts = localvars[_userparam_]
if opts != None: if opts != None:
# add this minimal check for a dict-like behavior rather # add this minimal check for a dict-like behavior rather
# than encountering a strange error later # than encountering a strange error later
@ -309,11 +344,11 @@ class Parameters(dict):
raise TypeError, \ raise TypeError, \
("The user parameter (variable/parameter `%s' in function `%s')" + ("The user parameter (variable/parameter `%s' in function `%s')" +
" is not a dict-like object)") \ " is not a dict-like object)") \
% (userparam, caller.f_code.co_name) % (_userparam_, caller.f_code.co_name)
contexts.append(opts) contexts.append(opts)
else: else:
if userparam in _opts_: if _userparam_ in _opts_:
opts = _opts_[userparam] opts = _opts_[_userparam_]
if opts != None: if opts != None:
# add this minimal check for a dict-like behavior rather # add this minimal check for a dict-like behavior rather
# than encountering a strange error later # than encountering a strange error later
@ -321,7 +356,7 @@ class Parameters(dict):
raise TypeError, \ raise TypeError, \
("The user parameter (variable/parameter `%s' in function `%s')" + ("The user parameter (variable/parameter `%s' in function `%s')" +
" is not a dict-like object)") \ " is not a dict-like object)") \
% (userparam, caller.f_code.co_name) % (_userparam_, caller.f_code.co_name)
contexts.append(opts) contexts.append(opts)
# then this own Parameters data will come here: # then this own Parameters data will come here:
@ -331,7 +366,7 @@ class Parameters(dict):
contexts += [ d for d in defaults ] contexts += [ d for d in defaults ]
# Now construct the Parameters() class for this calling function: # Now construct the Parameters() class for this calling function:
return Parameters(_kwparam_=kwparam, _userparam_=userparam, *contexts) return Parameters(_kwparam_=self._kwparam_, _userparam_=self._userparam_, *contexts)
#def __dict__(self): #def __dict__(self):
# return self._prm_ # return self._prm_

@ -1,8 +1,14 @@
# $Id: params_flat_test.py,v 1.1 2010-09-30 16:16:38 wirawan Exp $ # $Id: params_flat_test.py,v 1.2 2011-09-09 18:58:48 wirawan Exp $
# 20100930 # 20100930
from wpylib.params import flat as params from wpylib.params import flat as params
global_defaults = params(
nbasis= 275,
npart= 29,
deltau= 0.01,
)
def test1(): def test1():
defaults = { defaults = {
'nbasis': 320, 'nbasis': 320,
@ -10,7 +16,9 @@ def test1():
'deltau': 0.025, 'deltau': 0.025,
} }
p = params(defaults, nbasis=332) p = params(defaults, nbasis=332)
nbasis = 327
print "test1()"
print "self-defined values = ", p print "self-defined values = ", p
print "nbasis = ", p.nbasis print "nbasis = ", p.nbasis
print "npart = ", p.npart print "npart = ", p.npart
@ -19,6 +27,37 @@ def test1():
print "new deltau = ", p.deltau print "new deltau = ", p.deltau
def test2(**_opts_):
p = global_defaults._create_(_localvars_=1)
nbasis = 327
print "test2()"
print "self-defined values = ", p
print "nbasis = ", p.nbasis # gives 275 -- although _localvars_ already requested.
print "npart = ", p.npart
print "deltau = ", p.deltau
p.deltau = 0.01
print "new deltau = ", p.deltau
def test2b(**_opts_):
nbasis = 327
p = global_defaults._create_(_localvars_=1)
nbasis = 3270
print "test2()"
print "self-defined values = ", p
print "nbasis = ", p.nbasis # gives 327. Changes to local vars won't alter anything.
print "npart = ", p.npart
print "deltau = ", p.deltau
p.deltau = 0.01
print "new deltau = ", p.deltau
if __name__ == "__main__": if __name__ == "__main__":
test1() test1()
test2()
test2b()

Loading…
Cancel
Save