* Updating Parameters class: now based on dict.
* Can add two Parameters together, which means joining the two or more dicts, much like what function dict_join does.
This commit is contained in:
215
sugar.py
215
sugar.py
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/ipython -pylab
|
||||
#
|
||||
# $Id: sugar.py,v 1.5 2010-08-12 19:35:55 wirawan Exp $
|
||||
# $Id: sugar.py,v 1.6 2010-09-10 21:24:56 wirawan Exp $
|
||||
#
|
||||
# Created: 20100121
|
||||
# Wirawan Purwanto
|
||||
@@ -11,6 +11,7 @@
|
||||
#
|
||||
#
|
||||
import sys
|
||||
import weakref
|
||||
|
||||
def ifelse(cond, trueval, *args):
|
||||
"""An alternative to python's own ternary operator, but with multiple
|
||||
@@ -109,20 +110,24 @@ class ranges_type:
|
||||
ranges = ranges_type()
|
||||
|
||||
|
||||
class Parameters(object):
|
||||
class Parameters(dict):
|
||||
"""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.
|
||||
It scans through the namescopes (dicts) in a deterministic and sequential
|
||||
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.
|
||||
WARNING: This object is derived from python dict with ALL method names removed,
|
||||
so as to avoid collosion of these names with user-defined parameters with the
|
||||
same name.
|
||||
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.
|
||||
WARNING: Be careful modifying this class; endless recursive calls are
|
||||
possible.
|
||||
|
||||
|
||||
Some uses:
|
||||
|
||||
@@ -135,21 +140,118 @@ class Parameters(object):
|
||||
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.
|
||||
Parameters can also be updated in this way:
|
||||
|
||||
a = Parameters(...)
|
||||
updates = {'nblk': 7, 'nbasis': 32}
|
||||
a += updates
|
||||
|
||||
or, to call a function with a combination of parameters:
|
||||
|
||||
Reserved private members of the Parameters object:
|
||||
* _no_null_ = (True/False, default False) look for non-null (non-"None") 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.
|
||||
* _kwparam_ = (string, default "_opts_") the default name of the function argument
|
||||
that will hold excess named arguments.
|
||||
Used in _create_() function below.
|
||||
If this is set to None, we will not use this feature.
|
||||
* _userparam_ = (string, default "_p") the default name of the function argument
|
||||
that will contain Parameters-like object given by the user.
|
||||
Used in _create_() function below.
|
||||
If this is set to None, we will not use this feature.
|
||||
|
||||
The most overriding list of parameters, as provided via excess key=value
|
||||
arguments in creating this Parameters object, are stored in "self".
|
||||
"""
|
||||
|
||||
class _self_weakref_:
|
||||
"""A minimal proxy object, just enough to get a weakref to the 'self' object
|
||||
below to be accesible via a few dict-like lookup mechanisms.
|
||||
Also needed to avoid recursive `in' and [] get operators below."""
|
||||
def __init__(self, obj):
|
||||
self.ref = weakref.ref(obj)
|
||||
def __contains__(self, key):
|
||||
return dict.__contains__(self.ref(), key)
|
||||
def __getitem__(self, key):
|
||||
return dict.__getitem__(self.ref(), key)
|
||||
|
||||
def __init__(self, *_override_dicts_, **_opts_):
|
||||
"""
|
||||
Again, keyword arguments passed here will become the most overriding options.
|
||||
Creates a new Parameters() object.
|
||||
The unnamed arguments are taken to be dict-like objects from which we will
|
||||
search for parameters.
|
||||
We silently ignore `None' values which are passed in this way.
|
||||
Parameters will be searched in left-to-right order of these dict-like
|
||||
objects.
|
||||
Then the keyword-style arguments passed on this constructor will become
|
||||
the most overriding options.
|
||||
|
||||
The dict-like objects must contain the following functionalities:
|
||||
* for key in obj:
|
||||
...
|
||||
(in other words, the __iter__() method).
|
||||
* key in obj
|
||||
* obj[key]
|
||||
That's it!
|
||||
|
||||
Example:
|
||||
defaults = { 'walltime': '6:00:00', 'nwlk': 100 }
|
||||
# ...
|
||||
p = Parameters(defaults, walltime='7:00:00', nblk=300)
|
||||
|
||||
Then when we want to use it:
|
||||
>> p.nwlk
|
||||
100
|
||||
>> p.walltime
|
||||
'7:00:00'
|
||||
>> p.nblk
|
||||
300
|
||||
|
||||
Options:
|
||||
* _no_null_ = if True, look for the first non-None value.
|
||||
* _flatten_ = will flatten the key-value pairs.
|
||||
Note that this may make the Parameters object unnecessarily large in memory.
|
||||
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
|
||||
because of the shallow copying involved here.
|
||||
* _kwparam_
|
||||
* _userparam_
|
||||
At present, the `flatten' attribute will not be propagated to the child
|
||||
Parameters objects created by this parent object.
|
||||
"""
|
||||
prm = _opts_
|
||||
|
||||
# Remove standard dict procedure names not beginning with "_":
|
||||
for badkw in self.__dict__:
|
||||
if not badkw.startswith("_"):
|
||||
del self.__dict__[badkw]
|
||||
# Store the user-defined overrides in its own container:
|
||||
dict.clear(self)
|
||||
dict.update(self, _opts_)
|
||||
if _opts_.get('_flatten_', False):
|
||||
for p in paramlist:
|
||||
dict.update(self, p)
|
||||
else:
|
||||
# WARNING: Using weakref proxy is important:
|
||||
# - to allow clean deletion of Parameters() objects when not needed
|
||||
# - to avoid recursive 'in' and 'get[]' operators.
|
||||
paramlist = (Parameters._self_weakref_(self),) + _override_dicts_ #+ tuple(deflist))
|
||||
#paramlist = (self,) + _override_dicts_ #+ tuple(deflist))
|
||||
self.__dict__["_list_"] = [ p for p in paramlist if p != None ]
|
||||
self.__dict__["_kwparam_"] = _opts_.get("_kwparam_", "_opts_")
|
||||
self.__dict__["_userparam_"] = _opts_.get("_userparam_", "_p")
|
||||
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 ]
|
||||
# Finally, filter out reserved keywords from the dict:
|
||||
for badkw in ("_kwparam_", "_userparam_", "_no_null_", "_flatten_"):
|
||||
#if badkw in self: del self[badkw] -- recursive!!!
|
||||
if dict.__contains__(self,badkw): del self[badkw]
|
||||
def _copy_(self):
|
||||
"""Returns a copy of the Parameters() object."""
|
||||
return Parameters(_no_null_=self._no_null_,
|
||||
_kwparam_=self._kwparam_,
|
||||
_userparam_=self._userparam_,
|
||||
*self._list_[1:],
|
||||
**self)
|
||||
def __getattr__(self, key):
|
||||
"""Allows options to be accessed in attribute-like manner, like:
|
||||
opt.niter = 3
|
||||
@@ -162,12 +264,12 @@ class Parameters(object):
|
||||
else:
|
||||
for ov in self._list_:
|
||||
if key in ov: return ov[key]
|
||||
# Otherwise:
|
||||
return object.__getattribute__(self, key)
|
||||
# Otherwise: -- but most likely this will return attribute error:
|
||||
return dict.__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
|
||||
self[key] = value
|
||||
def __contains__(self, key):
|
||||
if self._no_null_:
|
||||
for ov in self._list_:
|
||||
@@ -184,70 +286,99 @@ class Parameters(object):
|
||||
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
|
||||
#def __setitem__(self, key, value): # -- inherited from dict
|
||||
# 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 __iter__(self): # -- inherited from dict
|
||||
# """Returns the iterator over key-value pairs owned by this object.
|
||||
# This does NOT return key-value pairs owned by the _override_dicts_.
|
||||
# """
|
||||
# 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):
|
||||
"""Updates the most overriding parameters with key-value pairs from
|
||||
srcdict.
|
||||
Srcdict can be either a dict-derived object or a Parameters-derived
|
||||
object."""
|
||||
dict.update(self, srcdict)
|
||||
def __add__(self, srcdict):
|
||||
"""Returns a copy of the Parameters() object, with the most-overriding
|
||||
parameters updated from the contents of srcdict."""
|
||||
rslt = self._copy_()
|
||||
rslt._update_(srcdict)
|
||||
return rslt
|
||||
def _create_(self, kwparam=None, userparam=None, *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.
|
||||
parameters, and where some parameters are to be overriden via function
|
||||
arguments, etc.
|
||||
|
||||
The order of lookup is definite:
|
||||
*
|
||||
* local variables of the calling subroutine will take precedence
|
||||
* the excess keyword-based parameters,
|
||||
* user-supplied Parameters-like object, which is
|
||||
* the dicts (passed in the `defaults' unnamed parameter list) is searched
|
||||
*last*.
|
||||
I suggest that this is used only as a last-effort safety net.
|
||||
Ideally, the creating Parameters object itself should contain the
|
||||
'factory defaults', as shown in the example below.
|
||||
|
||||
class Something(object):
|
||||
def __init__(self, ...):
|
||||
# self.opts holds the factory default
|
||||
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)
|
||||
# FIXME: use self-introspection to reduce kitchen-sink params here:
|
||||
p = self.opts._create_(_defaults_)
|
||||
# ^ This will create an equivalent of:
|
||||
# Parameters(locals(), _opts_, _opts_.get('opts'), self.opts, _defaults)
|
||||
# Now use it:
|
||||
if p.cleanup:
|
||||
... do something
|
||||
"""
|
||||
# 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
|
||||
if kwparam == None: kwparam = self._kwparam_
|
||||
if userparam == None: userparam = self._userparam_
|
||||
|
||||
# local variables will be the first scope 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]
|
||||
if kwparam in localvars:
|
||||
_opts_ = localvars[kwparam]
|
||||
contexts.append(_opts_)
|
||||
else:
|
||||
_opts_ = {}
|
||||
# then opts, an explicitly-defined argument carrying set of parameters
|
||||
if userparams in localvars:
|
||||
opts = localvars[userparams]
|
||||
# then opts, an explicitly-defined argument which contain a set of parameters
|
||||
if userparam in localvars:
|
||||
opts = localvars[userparam]
|
||||
contexts.append(opts)
|
||||
else:
|
||||
opts = {}
|
||||
if userparams in _opts_:
|
||||
contexts.append(_opts_[userparams])
|
||||
if userparam in _opts_:
|
||||
contexts.append(_opts_[userparam])
|
||||
|
||||
# then this own Parameters data will come here:
|
||||
contexts.append(self)
|
||||
|
||||
# then any defaults
|
||||
# then any last-minute defaults
|
||||
contexts += [ d for d in defaults ]
|
||||
|
||||
# Now construct the Parameters() class for this calling function:
|
||||
return Parameters(*contexts)
|
||||
|
||||
return Parameters(_kwparam_=kwparam, _userparam_=userparam, *contexts)
|
||||
|
||||
#def __dict__(self):
|
||||
# return self._prm_
|
||||
|
||||
Reference in New Issue
Block a user