Source code for scitools.PrmDictBase

#!/usr/bin/env python
"""
Module for managing parameters.
"""
import re, os, sys

[docs]def message(m): if os.environ.get('DEBUG', '0') == '1': print m
[docs]class PrmDictBase(object): """ Base class for managing parameters stored in dictionaries. Typical use includes data or solver classes for solving physical problems numerically. One may then choose to hold all physical parameters in a dictionary physical_prm, containing (parameter name, value) pairs, and all numerical parameters in a dictionary numerical_prm. The physical_prm and numerical_prm dictionaries can then be defined in a subclass of PrmDictBase and managed by PrmDictBase. The management includes several convenient features: - keeping all input data in one place - setting of one or more parameters where the type of the value must match the type of the previous (initial) value - pretty print of all defined parameters - copying parameters from dictionaries to, e.g., local variables and back again, or to local namespaces and back again - easy transition from parameter dictionaries to more sophisticated handling of input data, e.g., class scitools.ParameterInterface (GUI, CGI, command-line args) The subclass typically defines the dictionaries, say self.physical_prm and self.numerical_prm. Then these are appended to the inherited self._prm_list list to be registered. All members of this list are dictionaries that will not accept new keys (i.e., all parameters must be defined prior to registering them in self._prm_list). With this list one has a collection of all parameter dictionaries in the application. self._type_check[prm] is defined if we want to type check a parameter prm. if self._type_check[prm] is True (or False), prm must either be None, of the same type as the previously registered value of prm, or any number (float, int, complex) if the previous value prm was any number. Instead of a boolean value, self._type_check[prm] may hold a tuple of class types (to be used in isinstance checks), or a function which takes the value as argument and returns True if the that value is of the right type (otherwise False). In addition to the parameter dictionaries with fixed keys, class PrmDictBase also holds a self.user_prm, which is a dictionary of "meta data", i.e., an arbitrary set of keys and values that can arbitrarily extended anywhere. If self.user_prm is None, no such meta data can exists (implying that only parameters registered in the dictionaries in self._prm_list are allowed - the programmer of subclasses can of course extend these parameter sets whenever desired; disallowing a parameter name is only a feature of the set function for setting the value of a (registered) parameter). Here is an example:: from scitools.PrmDictBase import PrmDictBase class SomeSolver(PrmDictBase): def __init__(self, **kwargs): PrmDictBase.__init__(self) # register parameters in dictionaries: self.physical_prm = {'density': 1.0, 'Cp': 1.0, 'k': 1.0, 'L': 1.0} self.numerical_prm = {'n': 10, 'dt': 0.1, 'tstop': 3} # attach dictionaries to base class list (required): self._prm_list = [self.physical_prm, self.numerical_prm] # specify parameters to be type checked when set: self._type_check.update({'n': True, 'dt': (float,), 'k': lambda k: isinstance(int,float) and k>0}) # disallow arbitrary meta data self.user_prm = None # set to {} if meta data are allowed # initialize parameters according to keyword arguments: self.set(**kwargs) def _update(self): # dt depends on n, L, k; update dt in case the three # others parameters have been changed # (in general this method is used to check consistency # between parameters and perform updates if necessary) n = self.numerical_prm['n'] L = self.physical_prm['L'] k = self.physical_prm['k'] self.u = zeros(n+1, Float) h = L/float(n) dt_limit = h**2/(2*k) if self.numerical_prm['dt'] > dt_limit: self.numerical_prm['dt'] = dt_limit def compute1(self): # compute something return self.physical_prm['k']/self.physical_prm['Cp'] def compute2(self): # turn numerical parameters into local variables: exec self.dicts2variables(self._prm_list) # or exec self.dicts2variables(self.numerical_prm) # selected prms # now we have local variables n, dt, tstop, density, Cp, k, L # that we can compute with, say Q = k/Cp dt = 0.9*dt # if some of the local variables are changed, say dt, they must # be inserted back into the parameter dictionaries: self.variables2dicts(self.numerical_prm, dt=dt) """
[docs] def __init__(self): # dicts whose keys are fixed (non-extensible): self._prm_list = [] # fill in subclass self.user_prm = None # user's meta data self._type_check = {} # fill in subclass
def _prm_dict_names(self): """Return the name of all self.*_prm dictionaries.""" return [attr for attr in self.__dict__ if \ re.search(r'^[^_].*_prm$', attr)]
[docs] def usage(self): """Print the name of all parameters that can be set.""" prm_dict_names = self._prm_dict_names() prm_names = [] for name in prm_dict_names: d = self.__dict__[name] if isinstance(d, dict): k = d.keys() k.sort(lambda a,b: cmp(a.lower(),b.lower())) prm_names += k print 'registered parameters:\n' for i in prm_names: print i # alternative (sort all in one bunch): # names = [] # for d in self._prm_list: # names += d.keys() # names.sort # print names
[docs] def dump(self): """Dump all parameters and their values.""" for d in self._prm_list: keys = d.keys() keys.sort(lambda a,b: cmp(a.lower(),b.lower())) for prm in keys: print '%s = %s' % (prm, d[prm])
[docs] def set(self, **kwargs): """Set kwargs data in parameter dictionaries.""" # print usage message if no arguments: if len(kwargs) == 0: self.usage() return for prm in kwargs: _set = False for d in self._prm_list: if len(d.keys()) == 0: raise ValueError('self._prm_list is wrong (empty)') try: if self.set_in_dict(prm, kwargs[prm], d): _set = True break except TypeError, msg: print msg #break sys.exit(1) # type error is fatal if not _set: # maybe set prm as meta data? if isinstance(self.user_prm, dict): # not a registered parameter: self.user_prm[prm] = kwargs[prm] message('%s=%s assigned in self.user_prm' % \ (prm, kwargs[prm])) else: raise NameError('parameter "%s" not registered' % prm) self._update()
[docs] def set_in_dict(self, prm, value, d): """ Set d[prm]=value, but check if prm is registered in class dictionaries, if the type is acceptable, etc. """ can_set = False # check that prm is a registered key if prm in d: if prm in self._type_check: # prm should be type-checked if isinstance(self._type_check[prm], (int,float)): # (bool is subclass of int) if self._type_check[prm]: # type check against prev. value or None: if isinstance(value, (type(d[prm]), None)): can_set = True # allow mixing int, float, complex: elif operator.isNumberType(value) and\ operator.isNumberType(d[prm]): can_set = True elif isinstance(self._type_check[prm], (tuple,list,type)): # self._type_check[prm] holds either the type or # a tuple/list of types; test against them #print 'testing %s=%s against type %s' % (prm,value,self._type_check[prm]) if isinstance(value, self._type_check[prm]): can_set = True else: raise TypeError('\n\n%s=%s: %s has type %s, not %s' % \ (prm, value, prm, self._type_check[prm], type(value))) elif callable(self._type_check[prm]): can_set = self._type_check[prm](value) else: raise TypeError('self._type_check["%s"] has an '\ 'illegal value %s' % \ (prm, self._type_check[prm])) else: can_set = True else: message('%s is not registered in\n%s' % (prm, d)) if can_set: d[prm] = value message('%s=%s is assigned' % (prm, value)) return True return False
def _update(self): """Check data consistency and make updates.""" # to be implemented in subclasses pass
[docs] def get(self, **kwargs): return [self._solver_prm[prm] \ for prm in kwargs if prm in self._solver_prm]
[docs] def properties(self, global_namespace): """Make properties out of local dictionaries.""" for ds in self._prm_dict_names(): d = eval('self.' + ds) for prm in d: # or for prm in self.__dict__[ds] # properties cannot have whitespace: prm = prm.replace(' ', '_') cmd = '%s.%s = property(fget='\ 'lambda self: self.%s["%s"], %s)' % \ (self.__class__.__name__, prm, ds, prm, ' doc="read-only property"') print cmd exec cmd in global_namespace, locals()
[docs] def dicts2namespace(self, namespace, dicts, overwrite=True): """ Make namespace variables out of dict items. That is, for all dicts, insert all (key,value) pairs in the namespace dict. namespace is a dictionary, dicts is a list of dictionaries. """ # can be tuned in subclasses # allow dicts to be a single dictionary: if not isinstance(dicts, (list,tuple)): dicts = [dicts] for d in dicts: if overwrite: namespace.update(d) else: for key in d: if key in namespace and not overwrite: print 'cannot overwrite %s' % key else: namespace[key] = d[key]
[docs] def dicts2namespace2(self, namespace, dicts): """As dicts2namespace2, but use exec.""" # can be tuned in subclasses # allow dicts to be a single dictionary: if not isinstance(dicts, (list,tuple)): dicts = [dicts] for d in dicts: for key in d: exec '%s=%s' % (key,repr(d[key])) in globals(), namespace
[docs] def namespace2dicts(self, namespace, dicts): """ Update dicts from variables in a namespace. That is, for all keys in namespace, insert (key,value) pair in the dict in dicts that has the same key registered. namespace is a dictionary, dicts is a list of dictionaries. """ # allow dicts to be a single dictionary: if not isinstance(dicts, (list,tuple)): dicts = [dicts] keys = [] # all keys in namespace that are keys in dicts for key in namespace: for d in dicts: if key in d: d[key] = namespace[key] # update value keys.append(key) # mark for delete # clean up what we made in self.dicts2namespace: for key in keys: del namespace[key]
[docs] def dicts2variables(self, dicts): """ Make Python code string that defines local variables from all parameters in dicts (list of dictionaries of parameters). For example, if dicts[1] has a key n with value 1.0, the statement 'n=1.0' will be included in the returned string. The calling code will typically exec this returned string to make local variables (short hands) from parameters stored in dictionaries. (Note that such local variables are read-only, changing their values will not be reflected in the dictionaries!). """ # allow dicts to be a single dictionary: if not isinstance(dicts, (list,tuple)): dicts = [dicts] s = '' for d in dicts: for name in d: s += '%s = %s\n' % (name, d[name]) return s
[docs] def variables2dicts(self, dicts, **variables): """ Insert the name=value keyword arguments in variables into the dictionaries in dicts (list of dictionaries). This is the inverse of the dicts2variables function. Usage: exec self.dicts2variables(self.numerical_prm) # work with read-only n, dt, tstop ... # update (in case n, dt, tstop was changed): self.variables2dicts(self.numerical_prm, n=n, dt=dt, tstop=tstop) """ for name in variables: for d in dicts: if name in d: d[name] = variables[name] # initial tests are found in src/py/examples/classdicts.py