Python configparser getting and setting without exceptions

There a few ways to handle this depending on how complex you want to get.

The simplest way is probably just chaining logic together. ConfigParser defines has_option to safely check if an option exists for a section.

apple = config.has_option(section,'apple') and config.get(section,'apple') or None

Alternatively, if you know ahead of time which options should have values you can set the defaults dictionary when instantiating the parser. This has the advantage of retaining and raising any errors for sections you don't know about.

 myDefaults = {'apple':None,'banana':None,'pear':None}
 config = configparser.ConfigParser(defaults=myDefaults)

As stated by Wogan you can create a wrapper function, but you can easily just again use has_option like so:

def get_with_default(config,section,name,default)
    if config.has_option(section,name):
        return config.get(section,name)
    else:
        return default

An alternative approach:

ConfigParser.get offers a vars parameter that can be passed in, which is used as the primary lookup if it's provided, however, it ignores whether there exists a value for the option already on the section.

We can, therefore, use vars via ducktyping, but we'll change the behavior of .items() to the following:

  • If the config object has the option we're already looking for, we'll take that.
  • Otherwise, we'll return the default from vars.

Here's a very naive implementation:

class DefaultOption(dict):

    def __init__(self, config, section, **kv):
        self._config = config
        self._section = section
        dict.__init__(self, **kv)

    def items(self):
        _items = []
        for option in self:
            if not self._config.has_option(self._section, option):
                _items.append((option, self[option]))
            else:
                value_in_config = self._config.get(self._section, option)
                _items.append((option, value_in_config))
        return _items

In usage:

def read_config(section, location):
    config = configparser.ConfigParser()
    config.read(location)
    apple = config.get(section, 'apple',
                       vars=DefaultOption(config, section, apple=None))
    pear = config.get(section, 'pear',
                      vars=DefaultOption(config, section, pear=None))
    banana = config.get(section, 'banana',
                        vars=DefaultOption(config, section, banana=None))
    return apple, pear, banana

def save_to_config(section, location, apple, pear, banana):
    config = configparser.ConfigParser()

    config.read(location)
    if section not in config.sections():
        config.add_section(section)

    config.set(section, 'apple', apple)
    config.set(section, 'pear', pear)
    with open(location, 'wb') as cf:
        config.write(cf)

That being said, this is a little indirect, but perfectly valid.

Note, that this will still raise NoSectionError.

If you're trying to handle that as well, ConfigParser.ConfigParser takes a dict_type parameter, so you just instantiate the class with a defaultdict.

So, change configparser.ConfigParser() to configparser.ConfigParser(dict_type=lambda: defaultdict(list))

For all intents and purposes though, I'd probably use Lego's suggestions.

Update for original question edit

If you want to use the defaults keyword into ConfigParser, it might help to look at how the implementation is defined. Here's the ConfigParser.__init__() code for how defaults are initialized. You'll see that defaults are used completely differently than sections. To dive a bit deeper about the role they play during get(), look at the code for ConfigParser.get(). Basically, if the section isn't DEFAULTSECT, then a NoSectionError is thrown.

You have two ways to overcome this:

  1. Use the defaultdict idea I proposed above
  2. Modify your read_config function slightly
def read_config(section):
    defaults = {'apple': None,
                'pear': None,
                'banana': None }
    config = configparser.ConfigParser(defaults = defaults)
    config.read(location)
    if not config.has_section(section):
        config.add_section(section)

    apple = config.get(section,'apple')
    pear = config.get(section, 'pear')
    banana = config.get(section, 'banana')

    return apple, pear, banana

But I say, since this is not a one-liner, it opens up a lot more paths like the DefaultOption class I offered. We can make it even a bit less verbose.


You could write a wrapper function to have the dict.setdefault behavior if that's what you want:

def config_setdefault_get(config, section, key, default)
    result = default
    try:
        result = config.get(section, key)
    except NoSectionError:
        config.add_section(section)
        config.set(section, key, default)
    except NoOptionError:
        config.set(section, key, default)
    finally:
        return result