ruamel.yaml修改YAML为何在文件末尾额外新增expiration键值对?
问题背景
使用ruamel.yaml解析独立YAML文件,需求是更新文件内的expiration日期,同时保留全部原有内容。相同代码处理两个结构略有差异的文件时,一个运行正常,另一个会额外新增多余的expiration键值对。
测试文件原始内容
- File_one:
--- value: 600 another_value: 330 main_config::config::config: username: password: 'ENCRYPTED_PASSWORD' name: 'John Doe' role: 'admin' expiration: '2055-01-01' email: 'username@email.com'
- File_two:
--- main_config::config::config: username: password: 'ENCRYPTED_PASSWORD' name: 'John Doe' role: 'admin' expiration: '2055-01-01' email: 'username@email.com' another_config::config: setting1: setting2: setting3: setting4: false
异常输出
File_two处理后,会在another_config::config层级下额外新增和setting1同级的expiration键:
--- main_config::config::config: username: password: 'ENCRYPTED_PASSWORD' name: 'John Doe' role: 'admin' expiration: '2055-01-01' email: 'username@email.com' another_config::config: setting1: setting2: setting3: setting4: false expiration: '2055-01-01'
复现代码
with open(f"./path/to/file.yaml", 'r') as f: file = yaml.load(f) for k, v in file.items(): if isinstance(v, dict): for x, y in v.items(): if isinstance(y, dict): y['expiration'] = DATE # 写入更新后的内容 with open(f"./path/to/file.yaml", "w") as edit_file: yaml.dump(file, edit_file)
原因分析
核心问题是遍历逻辑存在缺陷:代码仅做了固定两层的字典遍历,且没有判断目标键是否原本存在,就直接强制写入expiration值。
两个文件输出差异完全由自身结构不同导致:
- 处理File_one时,根节点下只有
main_config::config::config的值是字典,该字典下只有username的值是字典,刚好就是需要修改的用户配置层级,完全匹配两层遍历的逻辑,因此不会产生多余写入,看起来运行正常。 - 处理File_two时,根节点下有两个值为字典的键:第一个
main_config::config::config下的username字典被正确修改;第二个another_config::config下的setting1同样是字典类型,被遍历逻辑命中,被强制写入了原本不存在的expiration键,这就是多余键值对的来源。
简单说,代码的实际逻辑是给所有处于第二层嵌套位置的字典都添加expiration键,File_one刚好没有其他符合条件的字典,所以没有触发bug,File_two存在额外的第二层嵌套字典,就出现了异常写入。
修复方案
不要用固定层数的遍历盲目写入,建议通过递归遍历所有节点,仅对原本就存在expiration键的字典做更新,避免新增多余键:
def update_expiration(node, target_date): if isinstance(node, dict): # 仅当当前层级已存在expiration键时才更新 if 'expiration' in node: node['expiration'] = target_date # 递归处理所有子节点 for val in node.values(): update_expiration(val, target_date) with open("./path/to/file.yaml", 'r') as f: content = yaml.load(f) update_expiration(content, DATE) with open("./path/to/file.yaml", "w") as edit_file: yaml.dump(content, edit_file)
内容的提问来源于stack exchange,提问作者anjoola
相关产品推荐
相关产品推荐

