如何在Python3中读写INI配置文件且保留注释?
嘿,这个问题我之前在项目里也遇到过!Python标准库自带的configparser默认会丢弃INI文件里的注释和原始格式,导致写回文件时注释全没了。不过有两个靠谱的解决方案,看你的需求选:
方案1:用第三方库configobj(推荐)
这个库就是专门为处理带注释的INI文件设计的,原生支持保留注释、空行、缩进和原始文件结构,比标准库的configparser更适合这种场景。
首先安装它:
pip install configobj
然后使用起来很简单:
from configobj import ConfigObj # 读取配置文件,自动保留所有注释和格式 cfg = ConfigObj('your_config.ini', encoding='utf-8', indent_type=' ') # indent_type可以指定缩进方式,比如空格或制表符 # 修改配置(和操作字典一样方便) cfg['DEFAULT']['parameter'] = 8 # 写回文件,注释完全保留 cfg.write()
它能处理各种复杂情况,比如行内注释、多行值、不同的注释符号,完全不用你操心格式丢失的问题。
方案2:自定义ConfigParser类(不依赖第三方库)
如果不想引入第三方依赖,你可以自己扩展标准库的ConfigParser,在读取时记录注释的位置和内容,写回时再把注释插回去。下面是一个简化版的实现,适合大多数简单的INI文件场景:
import configparser from collections import defaultdict class CommentedConfigParser(configparser.ConfigParser): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 存储section前的注释,和每个key前的注释 self.section_comments = defaultdict(list) self.key_comments = defaultdict(dict) def read(self, filenames, encoding=None): # 手动读取解析,记录注释 for filename in filenames if isinstance(filenames, list) else [filenames]: with open(filename, 'r', encoding=encoding) as f: current_section = None current_comments = [] for line in f: stripped_line = line.strip() # 跳过空行 if not stripped_line: continue # 处理注释行 if stripped_line.startswith(('#', ';')): current_comments.append(line.rstrip('\n')) continue # 处理section if stripped_line.startswith('[') and stripped_line.endswith(']'): current_section = stripped_line[1:-1].strip() self.section_comments[current_section] = current_comments current_comments = [] if not self.has_section(current_section): self.add_section(current_section) continue # 处理键值对 if '=' in stripped_line: key, value = stripped_line.split('=', 1) key = key.strip() value = value.strip() self.set(current_section, key, value) self.key_comments[current_section][key] = current_comments current_comments = [] continue def write(self, fileobject): # 写回时先处理DEFAULT section if 'DEFAULT' in self.sections(): # 写section前的注释 if self.section_comments['DEFAULT']: fileobject.write('\n'.join(self.section_comments['DEFAULT']) + '\n') fileobject.write('[DEFAULT]\n') # 写每个key和对应的注释 for key, value in self['DEFAULT'].items(): if key in self.key_comments['DEFAULT']: fileobject.write('\n'.join(self.key_comments['DEFAULT'][key]) + '\n') fileobject.write(f'{key} = {value}\n') fileobject.write('\n') # 处理其他sections for section in self.sections(): if section == 'DEFAULT': continue if self.section_comments[section]: fileobject.write('\n'.join(self.section_comments[section]) + '\n') fileobject.write(f'[{section}]\n') for key, value in self[section].items(): if key in self.key_comments[section]: fileobject.write('\n'.join(self.key_comments[section][key]) + '\n') fileobject.write(f'{key} = {value}\n') fileobject.write('\n')
使用这个自定义类的方式和标准库差不多:
# 读取配置 cfg = CommentedConfigParser() cfg.read('your_config.ini') # 修改配置 cfg.set('DEFAULT', 'parameter', '8') # 写回文件 with open('your_config.ini', 'w') as f: cfg.write(f)
注意:这个自定义类是简化实现,可能处理不了行内注释、多行值等复杂情况,如果你的INI文件结构复杂,还是推荐用configobj更稳妥。
内容的提问来源于stack exchange,提问作者buhtz




