Python配置解析器(支持重复键)

2 投票
2 回答
7059 浏览
提问于 2025-04-16 14:13

最近我开始为我正在做的一个Python项目写一个配置解析器。起初我没有使用configparser和configobj,因为我想支持一种这样的配置文件:

key=value
key2=anothervalue

food=burger
food=hotdog
food=cake icecream

简单来说,这个配置文件会经常通过SSH在命令行上进行编辑。所以我不想太在意缩进或空格(像YAML那样),但我也想避免在vi中出现多个值的键被换行的情况(可能会有10个以上的值)。这就是我想支持重复键的原因。

在我理想的情况下,当我向Python的配置对象请求食物时,它会返回一个列表,比如['汉堡', '热狗', '蛋糕', '冰淇淋']。如果没有定义食物的值,它会去一个默认的配置文件中查找,并给我这些值。

我已经实现了上述功能

然而,我的问题开始于我意识到我想保留行内注释等内容。我处理读取和写入配置文件的方式是将文件解码成内存中的字典,从字典中读取值,或者将值写入字典,然后再把这个字典输出到文件中。这种方法并不太好,无法很好地保留行的顺序和注释,这让我很烦恼。

A) ConfigObj看起来有我需要的所有功能,除了支持重复键。它希望我创建一个列表,但在SSH下手动编辑这个列表会很麻烦,因为会换行。我可以让configobj更适合SSH/vi使用吗?

B) 我自己做的解决方案是否有问题?有没有更好的方法来读取/写入/存储我的配置值?有没有简单的方法可以通过修改某一行来改变配置文件中的键值,然后从内存中重新写入整个配置文件?

2 个回答

0

我肯定会尽量利用标准库里的东西,如果可以的话。

配置解析器类的定义看起来是这样的:

class ConfigParser.SafeConfigParser([defaults[, dict_type[, allow_no_value]]])

注意这里的 dict_type 参数。当你提供这个参数时,它会用来构建字典对象,这些字典对象会包含各个部分的列表、每个部分里的选项,以及默认值。默认情况下,它使用的是 collections.OrderedDict。也许你可以在这里传入一些东西,以实现你想要的多键行为,然后就能享受到 ConfigParser 带来的所有好处。你可能需要自己写一个类来实现这个功能,或者你也可以在 PyPi 或 ActiveState 的食谱中找到别人写好的。试着找找“袋”或“多重集合”类。

我会选择这个方法,或者干脆忍耐一下,直接用列表:

foo = value1, value2, value3
0

一个疯狂的想法:把你的字典里的值做成一个包含三个元素的列表,每个元素包括行号、列号和实际的值,并且为注释添加一个特殊的键。

CommentSymbol = ';'
def readConfig(filename):
    f = open(filename, 'r')
    if not f:
        return
    def addValue(dict, key, lineIdx, colIdx, value):
        if key in dict:
            dict[key].append((lineIdx, colIdx, value))
        else:
            dict[key] = [(lineIdx, colIdx, value)]
    res = {}
    i = 0
    for line in f.readlines():
        idx = line.find(CommentSymbol)
        if idx != -1:
            comment = line[idx + 1:]
            addValue(res, CommentSymbol, i, idx, comment)
            line = line[:idx]
        pair = [x.strip() for x in line.split('=')][:2]
        if len(pair) == 2:
            addValue(res, pair[0], i, 0, pair[1])
        i += 1
    return res

def writeConfig(dict, filename):
    f = open(filename, 'w')
    if not f:
        return
    index = sorted(dict.iteritems(), cmp = lambda x, y: cmp(x[1][:2], y[1][:2]))
    i = 0
    for k, V in index:
        for v in V:
            if v[0] > i:
                f.write('\n' * (v[0] - i - 1))
            if k == CommentSymbol:
                f.write('{0}{1}'.format(CommentSymbol, str(v[2])))
            else:
                f.write('{0} = {1}'.format(str(k), str(v[2])))
            i = v[0]
    f.close() 

撰写回答