更新INI文件而不删除注释

27

考虑以下INI文件:

[TestSettings]
# First comment goes here
environment = test

[Browser]
# Second comment goes here
browser = chrome
chromedriver = default

...

我正在使用Python 2.7更新ini文件:

config = ConfigParser.ConfigParser()
config.read(path_to_ini)
config.set('TestSettings','environment',r'some_other_value')

with open(path_to_ini, 'wb') as configfile:
    config.write(configfile)

如何在不删除注释的情况下更新INI文件。 INI文件已更新,但注释已被删除。

[TestSettings]
environment = some_other_value

[Browser]
browser = chrome
chromedriver = default

你不能使用 ConfigParser 来完成这个任务,你需要使用其他的库。 - David Heffernan
你尝试过使用allow_no_value参数吗?https://dev59.com/oWw15IYBdhLWcg3wbLLU - markcial
1
allow_no_value对读取配置没有影响。也就是说,首先不会读取注释来进行记录... - Sriram Mahavadi
你找到如何做这个了吗?也许你可以添加你的解决方案? - Roger
不得不从INI文件切换到XML。 - sarbo
5个回答

25
在写回配置文件时,注释被清除的原因是写方法根本没有考虑注释。它只是写入键值对。
最简单的解决方法是使用自定义的注释前缀和allow_no_value = True初始化configparser对象。然后,如果我们想在文件中保留默认的"#"和";"作为注释行,我们可以使用comment_prefixes='/'指定另一个注释前缀,比如"/"。您可以阅读configparser文档这个部分以获取更多信息。
也就是说,要保留注释,您必须欺骗configparser,使其相信以"#"开头的行不是注释,而是没有值的键。有趣 :)
# set comment_prefixes to a string which you will not use in the config file
config = configparser.ConfigParser(comment_prefixes='/', allow_no_value=True)
config.read_file(open('example.ini'))
...
config.write(open('example.ini', 'w'))

很遗憾,这对我不起作用:cp = ConfigParser.ConfigParser(allow_no_value=True, comment_prefixes='/') TypeError: __init__() got an unexpected keyword argument 'comment_prefixes'也许这只适用于更新版本的Configparser。 - Mino_e
好的,我刚刚用Python3尝试了一下,它可以正常工作。但是在Python2中会打印出上面的错误信息。 - Mino_e
@eric.frederich 这是因为configparser有一个默认的转换器。你可以通过执行config.optionxform = lambda option: option来绕过它。请参见此链接 - Tomerikoo
不错的解决方法。对我来说,它仍然会删除某些注释,因为我使用了部分的批量编辑:config["SECTION"] = {"key1": "value1", "key2": "value2"}。因此请记住始终单独设置键值对。 - rayon
1
这并不是很有效 - 它无法处理重复的注释和文件顶部的注释。 - Addison
显示剩余2条评论

11

ConfigObj 在读写INI文件时保留注释,看起来符合你的要求。你所描述的场景的示例用法:

from configobj import ConfigObj

config = ConfigObj(path_to_ini)
config['TestSettings']['environment'] = 'some_other_value'
config.write()

7
ConfigObj已经过时,查看分支 https://github.com/DiffSK/configobj。 - Y.N

2
ConfigUpdater 可以更新 .ini 文件并保留注释:pyscaffold/configupdater。不过我不知道它是否适用于 Python 2。
根据文档,与 ConfigParser 相比,其主要区别在于:
- 对更新配置文件的干扰最小化 - 正确处理注释

0

ConfigObj 在几乎所有情况下都是最佳选择。

然而,它不支持没有三引号的多行值,就像 ConfigParser 一样。在这种情况下,一个可行的选择可以是 iniparse

例如:

[TestSettings]
# First comment goes here
multiline_option = [
        first line,
        second line,
    ]

您可以通过以下方式更新多行值。
import iniparse
import sys

c = iniparse.ConfigParser()
c.read('config.ini')
value = """[
    still the first line,
    still the second line,
]
"""
c.set('TestSettings', 'multiline_option', value=value)
c.write(sys.stdout)

0

除非configparser更改其实现方式,否则不在选项和部分中的所有项目都不会被读取,因此当您将其写回时,未读取的项目将丢失。 您可以按以下方式编写更新:

def update_config(file, section, option, value, comment: str = None):
    sectFound = False
    lineIdx = 0
    with open(file, 'r') as config:
        lines = config.readlines()
        lineCount = len(lines)
        for line in lines:
            lineIdx += 1
            if sectFound and line.startswith('['):  #next secion
                lineIdx += -1
                lines.insert(lineIdx, option + ' = ' + value)
                if comment is not None:
                    lineIdx += 1
                    lines.insert(lineIdx, option + ' = ' + comment)
                break
            elif sectFound and line.startswith(option + ' = '):
                lines.pop(lineIdx)
                lines.insert(lineIdx, option + ' = ' + value)
                if comment is not None:
                    lineIdx += 1
                    lines.insert(lineIdx, option + ' = ' + comment)
                break
            elif sectFound and lineIdx == lineCount:
                lineIdx += 1
                lines.insert(lineIdx, option + ' = ' + value + '\n')
                if comment is not None:
                    lineIdx += 1
                    lines.insert(lineIdx, comment + '\n')
                break
            if line.strip() == '[' + section + ']':
                sectFound = True
    with open(file, 'w') as cfgfile:
        cfgfile.writelines(lines)
        if sectFound == False:
            cfgfile.writelines('[' + section + ']\n' + option + ' = ' + value)
            if comment is not None:
                cfgfile.writelines(comment)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接