Another software design question: program-level globals
Robert Brewer
fumanchu at amor.org
Tue Feb 17 13:16:25 EST 2004
Robin Munn wrote:
> OK, here's another software design question, one that's been
> bugging me
> for a while now. What do y'all think is the best way to handle
> program-level globals, such as configuration option -- especially in
> multi-module programs?
>
> Here's one approach:
>
> --- main.py ---
> import my_globals as g
> import somefuncs
> import morefuncs
> from optparse import OptionParser
>
> def main():
> parser = OptionParser()
> parser.add_option( ... )
> g.opts, g.args = parser.parse_args()
>
> somefuncs.do_setup()
> morefuncs.do_some_more_setup()
>
> somefuncs.do_some_work()
>
> if __name__ == '__main__':
> main()
>
>
> --- somefuncs.py ---
> import my_globals as g
>
> def do_setup():
> if g.opts.some_option:
> do_it_one_way()
> else:
> do_it_a_different_way()
>
>
> --- morefuncs.py ---
> import my_globals as g
>
> def do_some_more_setup():
> for arg in g.args:
> do_something_with(arg)
>
>
> --- my_globals.py ---
> (empty file)
>
>
> This approach relies on the fact that modules only get imported once.
> Thus when each module imports my_globals, it gets the *same* object
> (which is then given the name g to make it easier to write later). So
> when the attributes of g are modified in the main() function, they can
> then be read by do_setup() and do_some_more_setup(), because
> the object
> that somefuncs.py and morefuncs.py call "g" is the same module object
> that main.py also calls "g".
That works, but isn't very clear IMO, if only because people aren't used
to using modules as empty containers like that. Generally, I do the same
thing, only with an explicit class to handle the "globals".
class Namespace(object):
config = {}
loaded = False
def __init__(self):
self.config = {u'UnitTypes': {}}
self.loaded = False
def load(self):
"""Process self.config."""
# Merge in any files mentioned in config['ConfigFiles']
configFiles = [x.strip() for x
in self.config.get(u"ConfigFiles",
u'').split(u',')]
for eachFile in configFiles:
graft(configCP(eachFile), self.config)
# Load logger.
try:
allOptions = self.config[u'Logging']
except KeyError:
pass
else:
fileName = allOptions[u'FileName']
level = getattr(logging, allOptions.get(u'Level', u'ERROR'),
u'ERROR')
formatter = logging.Formatter('%(asctime)s %(levelname)s
%(message)s')
hdlr = logging.FileHandler(fileName)
hdlr.setFormatter(formatter)
logger = logging.getLogger('DejavuMain')
logger.addHandler(hdlr)
logger.setLevel(level)
# Other loading stuff...
# Run startup functions.
try:
startups = self.config[u'Startup']
except KeyError:
pass
else:
startups = dict([(int(key), value)
for key, value in startups.items()])
keys = startups.keys()
keys.sort()
for index in keys:
func = xray.functions(startups[index])
func(currentUI())
self.loaded = True
def graft(newConfig, oldConfig):
"""Merge two configuration dictionaries.
String values will be overwritten; dict values will be updated."""
for key, value in newConfig.items():
if isinstance(value, dict):
if key in oldConfig.keys():
oldConfig[key].update(value)
else:
oldConfig[key] = value
else:
oldConfig[key] = value
def configCP(configFileName):
"""Processor for a ConfigParser file (for updating
Namespace.config)."""
parser = ConfigParser.ConfigParser()
parser.optionxform = unicode # Makes names
case-sensitive.
parser.read(configFileName)
conf = {}
for section in parser.sections():
conf[section] = dict(parser.items(section))
return conf
In your example, this class might be placed in my_globals.py. Since
modules *are* only loaded once, the other issue you might run into is
two instances of your app overwriting each others' globals. Having a
separate Namespace object for each app instance fixes that (hence the
name "Namespace").
Robert Brewer
MIS
Amor Ministries
fumanchu at amor.org
More information about the Python-list
mailing list