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