使用PyYAML处理注释数据时如何保留YAML中的引号
使用PyYAML处理注释数据时如何保留YAML中的引号
问题描述
我正在处理一个YAML文件,里面的注释带有需要保留引号的特定值。输入示例如下:
example.com/: 1. catalog-item-20='server1.test.local:80' 2. network-policy-version=v14.yaml openshift.io/: 3. sa.scc.mcs='s0,c107,c49' collectord.io/: 4. logs-index=channel_1 5. logs-override.11-match='^.*(%SENSITIVE%).*$'
我需要处理这个YAML,输出时对包含特殊字符的值保留正确的引号格式,期望输出如下:
annotations: example.com/catalog-item-20: 'server1.test.local:80' example.com/network-policy-version: v14.yaml openshift.io/sa.scc.mcs: 's0,c107,c49' collectord.io/logs-index: channel_1 collectord.io/logs-override.11-match: '^.*(%SENSITIVE%).*$'
需要注意:
- 输入中带引号的值,输出里要保留引号
- 输入中不带引号的值,输出里也保持不带引号
- 引号的保留要和值的内容无关(不管有没有特殊字符、逗号、冒号等)
我现在写了一段Python代码,用PyYAML来处理,但输出没保留住正确的引号:
import yaml with open('annotations.yaml', 'r') as f: raw_annotations = yaml.safe_load(f) annotations = {} for annotations_prefix, annotations_body in raw_annotations.items(): prefix = annotations_prefix if annotations_prefix.endswith('/') else f"{annotations_prefix}/" for value in annotations_body: if '=' in value: annotation_key, annotation_value = value.split('=', 1) if annotation_value.startswith("'") and annotation_value.endswith("'"): annotation_value = annotation_value[1:-1] full_key = f"{prefix}{annotation_key}" annotations[full_key] = annotation_value namespace_content = { 'apiVersion': 'v1', 'kind': 'Namespace', 'metadata': { 'annotations': annotations } } with open('namespace.yaml', 'w') as f: yaml.dump(namespace_content, f, default_flow_style=False)
这段代码生成的输出没有正确保留引号,比如:
annotations: example.com/catalog-item-20: server1.test.local:80 openshift.io/sa.scc.mcs: s0,c107,c49
我已经尝试了几种方法,但都没解决:
- 设置
default_style='"'或者default_style="'",但这样会给所有值都加上引号 - 用ruamel.yaml并设置
preserve_quotes=True,但没起作用 - 尝试了yamlcore包,同样无法保留引号
有没有办法让PyYAML完全按照输入文件里的样子保留引号?
解决方案
PyYAML本身的设计是将YAML解析为Python原生数据类型,会丢失所有原始格式信息(包括引号),所以没法直接用它实现你的需求。推荐使用ruamel.yaml——它专门为保留YAML原始格式而设计,能完美解决这个问题。
步骤1:安装ruamel.yaml
pip install ruamel.yaml
步骤2:修改代码
我们需要用ruamel的特殊字符串类型标记不同的引号状态,同时开启格式保留:
from ruamel.yaml import YAML from ruamel.yaml.scalarstring import SingleQuotedScalarString, PlainScalarString # 初始化YAML对象,开启引号保留 yaml = YAML() yaml.preserve_quotes = True with open('annotations.yaml', 'r') as f: raw_annotations = yaml.load(f) annotations = {} for annotations_prefix, annotations_body in raw_annotations.items(): # 确保前缀结尾带有斜杠 prefix = annotations_prefix if annotations_prefix.endswith('/') else f"{annotations_prefix}/" for value in annotations_body: if '=' in value: # 移除输入中的序号前缀(比如"1. ") cleaned_entry = value.split('. ', 1)[1] annotation_key, annotation_value = cleaned_entry.split('=', 1) full_key = f"{prefix}{annotation_key}" # 根据输入的引号状态,转换为对应的ScalarString类型 if annotation_value.startswith("'") and annotation_value.endswith("'"): # 去掉引号后,标记为单引号字符串 annotations[full_key] = SingleQuotedScalarString(annotation_value[1:-1]) else: # 标记为无引号的纯字符串 annotations[full_key] = PlainScalarString(annotation_value) # 构建Namespace配置 namespace_content = { 'apiVersion': 'v1', 'kind': 'Namespace', 'metadata': { 'annotations': annotations } } # 写入输出文件 with open('namespace.yaml', 'w') as f: yaml.dump(namespace_content, f)
为什么这个方法有效?
- ruamel.yaml的格式保留特性:它会跟踪YAML元素的原始格式信息,而不是像PyYAML那样只解析成原生类型
- ScalarString类型标记:
SingleQuotedScalarString和PlainScalarString明确告诉ruamel要输出的字符串格式,确保引号状态和输入一致 - preserve_quotes=True:阻止ruamel自动修改引号格式,严格按照标记输出
你之前用ruamel失败的原因
你之前可能只是开启了preserve_quotes=True,但没有将split出来的字符串转换为对应的ScalarString类型。因为split得到的是普通Python字符串,ruamel无法识别它原本的引号状态,所以需要手动标记类型才能保留格式。
这样处理后,输出的YAML就会完全符合你的期望,严格保留输入中的引号状态!
备注:内容来源于stack exchange,提问作者user2339149
相关产品推荐
相关产品推荐

