背景
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 ConfigParserif __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 DEFAULTSECTclass 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 cachecomment_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())continuevalue = 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_cachecomment_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_cachecomment_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 commentswith open(filename, encoding=encoding) as fp:self._read_comments(fp)except OSError:continueif isinstance(filename, os.PathLike):filename = os.fspath(filename)read_ok.append(filename)return read_okdef 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
实现的可能不太严谨,大家根据需要可以修改实现更有扩展性的功能。
参考:
Python 使用ConfigParser写入注释到文件中|极客教程
保留注释换行的python模块configparser