背景
python语言用来解析配置文件的模块是ConfigParser,python3中是configparser模块,在使用中经常用到write方法将配置项重新写入文件:
config.ini文件:
# 数据库配置
[database]
# 主机
# IP
host = localhost
# 端口
port = 3306
# 用户名
username = my_user
# 密码
password = my_password
# 日志配置
[logging]
# 日志等级
level = debug
# 输出格式
output = log.txt
代码:
from configparser import ConfigParser
if __name__ == '__main__':
# 创建ConfigParser对象
config = ConfigParser()
# 读取配置文件
config.read('config.ini', encoding='utf-8')
# 在配置文件中修改某个配置项的值
config.set('database', 'port', '3307') # 修改port为3307
# 写入配置文件,保留原有注释
with open('config.ini', 'w', encoding='utf-8') as configfile:
config.write(configfile)
结果:
[database]
host = localhost
port = 3307
username = my_user
password = my_password
[logging]
level = debug
output = log.txt
结果发现配置文件中的空行和注释行都会被去掉,虽然这个并不影响使用,但配置文件的可读性无疑还是变差了。
解决办法
为此特地对ConfigParser模块进行了一点改动,使其保留注释项和空行。
思路:
就是在读配置文件的时候碰到注释行或换行就缓存起来,然后在写入的时候从缓存中取出就可以了。
实现代码:
import os
from configparser import ConfigParser
from configparser import DEFAULTSECT
class MyConfigParser(ConfigParser):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.comment_line_dict = {}
def _read_comments(self, fp):
"""
read comments
"""
# comment or blank line temp cache
comment_line_cache = []
sections = []
for _, line in enumerate(fp):
# comment or blank line?
if line.strip() == '' or line[0] in self._comment_prefixes:
comment_line_cache.append(line.strip())
continue
value = line.strip()
# is it a section header?
mo = self.SECTCRE.match(value)
if mo:
section_name = mo.group('header')
self.comment_line_dict[section_name] = comment_line_cache
comment_line_cache = []
sections.append(section_name)
# an option line?
else:
mo = self._optcre.match(value)
if mo:
optname, vi, optval = mo.group('option', 'vi', 'value')
optname = self.optionxform(optname.rstrip())
self.comment_line_dict["%s.%s" % (sections[-1], optname)] = comment_line_cache
comment_line_cache = []
def read(self, filenames, encoding=None):
"""
Rewrite the read method of the parent class
"""
if isinstance(filenames, (str, bytes, os.PathLike)):
filenames = [filenames]
read_ok = []
for filename in filenames:
try:
with open(filename, encoding=encoding) as fp:
self._read(fp, filename)
# Add methods for reading comments
with open(filename, encoding=encoding) as fp:
self._read_comments(fp)
except OSError:
continue
if isinstance(filename, os.PathLike):
filename = os.fspath(filename)
read_ok.append(filename)
return read_ok
def write(self, fp, space_around_delimiters=True):
"""Write an .ini-format representation of the configuration state."""
if self._defaults:
comment_line = self.comment_line_dict.get("%s" % (DEFAULTSECT), [])
if comment_line:
fp.write("\n".join(comment_line) + "\n")
fp.write("[%s]\n" % DEFAULTSECT)
for (key, value) in self._defaults.items():
comment_line = self.comment_line_dict.get("%s.%s" % (DEFAULTSECT, key), [])
if comment_line:
fp.write("\n".join(comment_line) + "\n")
fp.write("%s = %s\n" % (key, str(value).replace('\n', '\n\t')))
fp.write("\n")
for section in self._sections:
comment_line = self.comment_line_dict.get("%s" % (section), [])
if comment_line:
fp.write("\n".join(comment_line) + "\n")
fp.write("[%s]\n" % section)
for (key, value) in self._sections[section].items():
comment_line = self.comment_line_dict.get("%s.%s" % (section, key), [])
if comment_line:
fp.write("\n".join(comment_line) + "\n")
if (value is not None) or (self._optcre == self.OPTCRE):
key = " = ".join((key, str(value).replace('\n', '\n\t')))
fp.write("%s\n" % (key))
fp.write("\n")
if __name__ == '__main__':
# 创建ConfigParser对象
# config = ConfigParser()
config = MyConfigParser()
# 读取配置文件
config.read('config.ini', encoding='utf-8')
# 在配置文件中修改某个配置项的值
config.set('database', 'port', '3307') # 修改port为3307
# 写入配置文件,保留原有注释
with open('config.ini', 'w', encoding='utf-8') as configfile:
config.write(configfile)
结果:
# 数据库配置
[database]
# 主机
# IP
host = localhost
# 端口
port = 3307
# 用户名
username = my_user
# 密码
password = my_password
# 日志配置
[logging]
# 日志等级
level = debug
# 输出格式
output = log.txt
实现的可能不太严谨,大家根据需要可以修改实现更有扩展性的功能。
参考: