注:作者编程小白,高手勿喷,如有疏漏,还请指正!
因业余爱好需要,发现现有的 ConfigParser 模块在写入数据时会删除注释,不利于配置文件对于用户而言的可读性,网上找了一圈,看得云里雾里,所以尝试自己在不通过修改源文件(也没这能力)的情况下,来临时解决一下问题。
例,原 test.ini 文件内容如下
[sect1]
# cmt 1.1
opt1 = 1
# cmt 1.2
opt2 = 2
# cmt 1.3
opt3 = 0
[sect2]
# cmt 2.1
opt1 = 3
# cmt 2.2
opt2 = 4
直接使用 ConfigParser 更改 opt3 的值为 33,python 代码如下
from configparser import ConfigParser
cfg = ConfigParser()
cfg.read("test.ini", encoding = "utf-8")
cfg.set("sect1", "opt3", "33")
with open("test.ini", "w+") as f:
cfg.write(f)
f.close()
运行结果如下,可以看到原有的 comment 都丢失了,
[sect1]
opt1 = 1
opt2 = 2
opt3 = 33
[sect2]
opt1 = 3
opt2 = 4
临时的解决方案如下,但很显然存在诸多问题,目前只能覆盖修改存在 section / option 对应 value 的场景,待后续深入研究再想办法解决(貌似理论上无解啊,新增/删除怎么搞?)
class keepCommentConfigParser():
def __init__(self, filePath, commentPrefixes): # commentPrefixes 需要用户输入 [],感觉这个写法不太对,最好是可以默认"#", ";" 并可以获取用户自定义字符
self.__filePath = filePath
self.__commentPrefixes = commentPrefixes
self.__replacePrefix = datetime.datetime.now().strftime('%Y%m%d%H%M%S') # 临时时间戳,解决方案的核心
self.__oriLines = [] # 用来存储带有注释的源文件内容,保留着,后面或许有用
self.__markupLines = [] # 用来存储用临时时间戳代替指定的注释符后的内容
self.__backup()
self.__updatedLines = [] # 用来存储被原 ConfigParser “践踏”后的新内容
def __backup(self):
# 直接复制并重命名原 .ini 文件为 .txt 文件,此处可能会有解析上的问题吧,暂未遇到
backupFilePath = self.__replacePrefix + ".txt"
shutil.copyfile(self.__filePath, backupFilePath)
with open(backupFilePath, encoding = "utf-8") as f:
self.__oriLines = f.readlines() # 获取带有注释的源文件内容
self.__markupLines = self.__oriLines[:] # 复制带有注释的源文件内容
f.close()
os.unlink(backupFilePath) # 永久删除临时的 .txt 文件
self.__markupComments()
def __markupComments(self):
if len(self.__commentPrefixes) > 0:
for i in range(len(self.__markupLines)):
for commentPrefix in self.__commentPrefixes:
if self.__markupLines[i].startswith(commentPrefix):
self.__markupLines[i] = self.__replacePrefix + self.__markupLines[i] # self.__markupLines 中,遇到指定注释符开头的行,则直接在行头加上临时时间戳,最终生成新的 self.__markupLines
break
def update(self):
# 读取经过原 ConfigParser “践踏”过后的 test.ini 文件
with open(self.__filePath, encoding = "utf-8") as f:
self.__updatedLines = f.readlines()
f.close()
validLineId = 0
for i in range(len(self.__markupLines)):
# 如果 self.__markupLines 中的一行不以临时时间戳开头,则这一行直接复制“践踏”过后的 test.ini 的对应行的值(所以只能应用在修改值的场景,增减就 gg 了)
if not self.__markupLines[i].startswith(self.__replacePrefix):
self.__markupLines[i] = self.__updatedLines[validLineId]
validLineId += 1
# 如果 self.__markupLines 中的一行以临时时间戳开头,则直接去掉临时时间戳
else:
self.__markupLines[i] = self.__markupLines[i].replace(self.__replacePrefix, "")
# 更新完 self.__markupLines 的所有行,重新写入文件
with open(self.__filePath, encoding = "utf-8", mode = "w+") as f:
f.writelines(self.__markupLines)
f.close()
if __name__ == "__main__":
kCCP = keepCommentConfigParser("test.ini", ["#", ";"])
cfg = ConfigParser(allow_no_value = True) # 这个 allow_no_value = True 貌似是重点
cfg.read("test.ini", encoding = "utf-8")
cfg.set("sect1", "opt3", "33")
with open("test.ini", "w+") as f:
cfg.write(f)
f.close()
kCCP.update()
此时程序跑下来就是,
[sect1]
# cmt 1.1
opt1 = 1
# cmt 1.2
opt2 = 2
# cmt 1.3
opt3 = 33
[sect2]
# cmt 2.1
opt1 = 3
# cmt 2.2
opt2 = 4
问题颇多,不过可以临时解决我只需要“修改值的场景”,还请大神们指点指点!