Python configparser 获取和设置时不抛出异常

14 投票
6 回答
25647 浏览
提问于 2025-04-18 13:53

每次你尝试用 Python 的 configparser 来获取或设置某个部分时,如果这个部分不存在,就会抛出一个 NoSectionError 错误。有没有办法避免这个问题呢?另外,当获取某个选项时,我能否也避免 NoOptionError 错误呢?

举个例子,使用字典时,有一个 setdefault 方法:当你查找一个不存在的键时,它不会抛出 KeyError 错误,而是会自动添加这个键,把它的值设置为默认值,并返回这个默认值。

我现在获取属性的方式是这样的:

def read_config(section):
    config = configparser.ConfigParser()
    config.read(location)
    try:
        apple = config.get(section, 'apple')
    except NoSectionError, NoOptionError:
        apple = None
    try:
        pear = config.get(section, 'pear')
    except NoSectionError, NoOptionError:
        pear = None
    try:
        banana = config(section, 'banana')
    except NoSectionError, NoOptionError:
        banana = None
    return apple, pear, banana

设置属性的方式是这样的:

def save_to_config(section, apple, pear, banana):
    config = configparser.ConfigParser()
    if not os.path.exists(folder_location):
        os.makedirs(folder_location)

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

    config.set(section, 'apple', apple)
    config.set(section, 'pear', pear)
    config.set(section, 'banana', banana)

设置属性还好,因为它们都在同一个部分,但获取属性就... 糟糕透了。肯定有更好的方法。

有没有什么一行代码可以把这个:

try:
    apple = config.get(section, 'apple')
except NoSectionError, NoOptionError:
    apple = None

简化成这个:

apple = config.get_with_default(section, 'apple', None)

-- 编辑 --

我根据 lego 的建议尝试做了以下修改:

def read_config(section):
    defaults = { section : {'apple': None,
                            'pear': None,
                            'banana': None }} 
    config = configparser.ConfigParser(defaults = defaults)
    config.read(location)

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

    return apple, pear, banana

但如果这个部分不存在,还是会抛出 NoSectionError 错误。

注意:我也尝试过把 defaults 设置为 {'apple': None, 'pear': None, 'banana': None }(没有部分)。

6 个回答

2

如果你想要实现 dict.setdefault 的功能,可以写一个包装函数来达到这个效果:

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
2

在一个包装函数中处理异常可能会更有效。如果你真的想避免使用异常处理,可以用一个复合的 if 语句来解决这个问题。

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

看起来你在问题中已经有了答案:

def get_with_default(section, name, default)
    try:
        return config.get(section, name)
    except (NoSectionError, NoOptionError):
        return default

另外一个方法是可以先设置一个dict(字典),里面包含每个部分的默认值,然后在从文件加载配置之前,先调用read_dict(defaults)。这样可以确保每个部分都有内容,这样就不会出现缺少部分的情况,也就不会抛出异常了。

12

处理这个问题的方法有几种,具体取决于你想要多复杂。

最简单的方法可能就是把逻辑串在一起。ConfigParser 提供了一个叫 has_option 的功能,可以安全地检查某个部分是否存在某个选项。

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

另外,如果你提前知道哪些选项应该有值,可以在创建解析器的时候设置一个 defaults 字典。这样做的好处是可以保留并抛出你不知道的部分的任何错误。

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

正如 Wogan 所说,你可以创建一个包装函数,但你也可以很简单地再次使用 has_option,像这样:

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

这里有一种替代的方法:

ConfigParser.get 提供了一个 vars 参数,如果你提供了这个参数,它会被用作主要的查找方式,但它不会考虑该选项在该部分是否已经有值。

因此,我们可以通过鸭子类型(duck typing)使用 vars,但我们会把 .items() 的行为改成以下方式:

  • 如果配置对象中已经有我们要找的选项,我们就用这个。
  • 否则,我们就返回 vars 中的默认值。

下面是一个非常简单的实现:

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

使用示例:

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)

不过,这种方法有点间接,但完全有效。

需要注意的是,这仍然会引发 NoSectionError 错误。

如果你也想处理这个错误,ConfigParser.ConfigParser 接受一个 dict_type 参数,所以你只需用 defaultdict 来实例化这个类。

所以,把 configparser.ConfigParser() 改成 configparser.ConfigParser(dict_type=lambda: defaultdict(list))

不过,出于所有目的,我可能会使用 Lego 的建议。

关于原问题更新

如果你想在 ConfigParser 中使用 defaults 关键字,查看一下实现是如何定义的可能会有帮助。这里是 ConfigParser.__init__() 的代码,可以看到默认值是如何初始化的。你会发现默认值的使用方式与部分是完全不同的。想更深入了解它们在 get() 中的作用,可以查看 ConfigParser.get() 的代码。基本上,如果该部分不是 DEFAULTSECT,就会抛出 NoSectionError

你有两种方法可以解决这个问题:

  1. 使用我上面提到的 defaultdict 的想法
  2. 稍微修改一下你的 read_config 函数
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

不过我觉得,既然这不是一行代码就能解决的问题,那就会有更多的选择,比如我提供的 DefaultOption 类。我们甚至可以让它变得更简洁一些。

撰写回答