I'm working on quite complicated scientific project.
I decided to use a configuration file for model description. However it was quite complicated to parse all strings after ConfigParser
and convert every option to required objects, so I would like to avoid this long/boring code.
I wrote very simple parser which converts all options into Python objects by default. With some additional name resolving I've got a simple, elegant and useful solution.
import os,sys,ConfigParser, types
import logging
from ConfigParser import ConfigParser
import numpy as np
import scipy as sp
def nameresolv(item,nspace):
copir = item.split("@")
if len(copir) < 2:
res = item
else:
res = ''
for pre,var in map(None,copir[::2],copir[1::2]):
if pre != None: res += pre
if var == None: continue
var = var.split(":")
if len(var) != 2: return None
if not var[0] in nspace : return None
if not var[1] in nspace[var[0]] : return None
res += 'nspace["%s"]["%s"]'%tuple(var)
copir = res.split("$")
if len(copir) < 2: return res
res = ''
for pre,var in map(None,copir[::2],copir[1::2]):
if pre != None: res += pre
if var == None: continue
var = var.split(":")
if len(var) != 2: return None
if not var[0] in nspace : return None
if not var[1] in nspace[var[0]] : return None
if type(nspace[var[0]][var[1]]) is types.LambdaType and nspace[var[0]][var[1]].__name__ == '<lambda>':
res += 'nspace["%s"]["%s"]'%tuple(var)
else:
res += str(nspace[var[0]][var[1]])
return unicode(res)
def confreader(filename,nspace = {}):
"""
Reads file with configurations and returns dictionary with sections
and options. All options will be turned into python objects (DON'T
FORGET PUT ALL STRING OPTIONS WITHIN QUOTES).
You can use @SECTION:OPTION@ notation to refer to existed python object,
or $SECTION:OPTION$ to convert object back into a string and insert a string.
Returns option dictionary or {}.
Empty dictionary indicates error with file opening, reading or parsing.
If confreader couldn't turn option into some python object, this
options is skipped and Warning message will put in logger.
"""
if filename == None:
return nspace
if not os.access(filename,os.R_OK):
return nspace
config = ConfigParser()
config.optionxform=str
try:
config.read( filename )
except :
return nspace
for section in config.sections():
if not section in nspace: nspace[section]={}
for option in config.options(section):
if option in nspace[section]:
logging.error("Name conflict option \'%s\' exists in section [\'%s\']"%(option,section) )
return {}
xitem = unicode( config.get(section,option) )
item = nameresolv(xitem,nspace)
if item == None:
logging.error("Problem with resolving option in [\'%s\']\'%s\'=\'%s\'"%(section,option,item) )
return {}
try:
exec "nspace[\""+section+"\"][\""+option+"\"]="+item
except :
logging.warning("Problem with reading configuration from the %s"%filename)
logging.warning("Cannot read section: \'%s\', option: \'%s\'"%(section,option) )
logging.warning(" %s"%item)
logging.warning("!!!! SKIPPED IT !!!!")
pass
return nspace
An example:
#FILE: examples.cfg
[LINKS]
x = ["a","b"]
y = [ @LINKS:x@, "c"]
z = ["x"]+@LINKS:x@+["c","d"]
[COMPUTATIONS]
x = 5
y = 7
x+y = @COMPUTATIONS:x@+@COMPUTATIONS:y@
x*y = @COMPUTATIONS:x@*@COMPUTATIONS:y@
lst = range(@COMPUTATIONS:x*y@)
filter = @COMPUTATIONS:lst@[@COMPUTATIONS:x@:@COMPUTATIONS:y@]
[FUNCTIONS]
fun = lambda x: x**2+32
operation = @FUNCTIONS:fun@(12)
fun(x+y) = @FUNCTIONS:fun@(@COMPUTATIONS:x+y@)
[LINKS_AND_STRINGS]
x = "I"
y = "Python"
exmp1 = @LINKS_AND_STRINGS:x@+' love '+@LINKS_AND_STRINGS:y@
#same but with string resolving. Please not that sting inside ' '
exmp2 = '$LINKS_AND_STRINGS:x$ love $LINKS_AND_STRINGS:y$'
#BUT this will create an a problem
#exmp3 = '@LINKS_AND_STRINGS:x@ love @LINKS_AND_STRINGS:y@'
#You can resolve variable into string
exmp4 = 'In my $LINKS_AND_STRINGS:y$ + conf, $LINKS_AND_STRINGS:x$ can resolve x+y = $COMPUTATIONS:x+y$ inline'
#END examples.cfg
And the result of parsing:
>>> from pyconf import pyconf
>>> cfg = pyconf("examples.cfg")
>>> for name in cfg: print name
...
COMPUTATIONS
FUNCTIONS
LINKS
LINKS_AND_STRINGS
>>>
>>> for name in cfg["LINKS"]: print name,"=",cfg["LINKS"][name]
...
y = [['a', 'b'], 'c']
x = ['a', 'b']
z = ['x', 'a', 'b', 'c', 'd']
>>>
>>> for name in cfg["COMPUTATIONS"]: print name,"=",cfg["COMPUTATIONS"][name]
...
filter = [5, 6]
lst = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]
x+y = 12
y = 7
x = 5
x*y = 35
>>>
>>> for name in cfg["FUNCTIONS"]: print name,"=",cfg["FUNCTIONS"][name]
...
fun = <function <lambda> at 0xa5841ec>
operation = 176
fun(x+y) = 176
>>>
>>> for name in cfg["LINKS_AND_STRINGS"]: print name,"=",cfg["LINKS_AND_STRINGS"][name]
...
y = Python
x = I
exmp4 = In my Python + conf, I can resolve x+y = 12 inline
exmp1 = I love Python
exmp2 = I love Python
>>>
I'd like to ask Python gurus for their opinion and any suggestions on code improvement. I'll appreciate any ideas on making this code better. Yep, feel free to use it if you'd like.