Python使用yaml模块解析YAML文件时如何保留字符串原有引号
问题说明
在实现YAML文件读取解析、将data标签下内容写入新YAML文件的需求时,使用yaml.load()无法区分带引号字符串与整型值、导出时会丢失字符串原有引号。
示例输入YAML文件内容:
data: key1: "Value1" key2: Value2
当前使用的Python解析代码:
import io import yaml test_dict={} with open("sample-string.yaml", "r") as stream: try: f = yaml.load(stream) test_dict['data'] = f['data'] except yaml.YAMLError as exc: print(exc) with io.open("output.yaml", "a",encoding="utf-8") as wf: try: yaml.dump(test_dict['data'],wf,allow_unicode=True,default_flow_style=False) except yaml.YAMLError as exc: print(exc)
运行上述代码生成的输出为:
key1: Value1 key2: Value2
key1对应的字符串值丢失了原有的双引号,不符合预期,预期输出应为:
key1: "Value1" key2: Value2
解决方案
原生PyYAML默认不会保留源文件的格式元信息(比如字符串是否带引号、引号类型),加载阶段就会把带双引号的普通字符串和不带引号的普通字符串统一解析为Python的str类型,丢失格式标记,所以dump阶段自然无法还原原有引号。要实现保留原引号的需求,有两种可行方案:
方案1:使用ruamel.yaml库替代原生PyYAML(推荐)
ruamel.yaml是专门为保留YAML原有格式设计的解析库,是处理这类格式保留需求的首选方案,对单引号、双引号、块格式等各类YAML写法的格式保留兼容性远好于原生PyYAML二次开发的方案。
- 安装依赖:
pip install ruamel.yaml - 修改后的可运行代码:
from ruamel.yaml import YAML yaml = YAML() # 开启原引号保留配置 yaml.preserve_quotes = True test_dict = {} with open("sample-string.yaml", "r", encoding="utf-8") as stream: f = yaml.load(stream) test_dict['data'] = f['data'] with open("output.yaml", "a", encoding="utf-8") as wf: yaml.dump(test_dict['data'], wf)
运行后输出的文件会完整保留key1原有的双引号格式,同时不会改动其他无引号字段的写法。
方案2:自定义PyYAML的构造器与表示器(仅适合简单场景)
如果必须使用原生PyYAML,可以自定义带引号标记的字符串子类,在加载时识别双引号字符串做标记,导出时针对标记的字符串强制输出双引号。但这种方案无法适配复杂嵌套场景、其他引号类型(单引号、折叠块标记等),维护成本高,不推荐在生产环境使用,示例代码如下:
import io import yaml from yaml.resolver import BaseResolver # 定义带双引号标记的字符串类型 class QuotedString(str): pass # 加载时识别双引号包裹的标量,转换为QuotedString类型 def quoted_constructor(loader, node): value = loader.construct_scalar(node) # 仅对原本带双引号的字符串打标记 if node.style == '"': return QuotedString(value) return value yaml.add_constructor(BaseResolver.DEFAULT_SCALAR_TAG, quoted_constructor, Loader=yaml.SafeLoader) # 导出时遇到QuotedString类型,强制使用双引号输出 def quoted_representer(dumper, data): return dumper.represent_scalar(BaseResolver.DEFAULT_SCALAR_TAG, data, style='"') yaml.add_representer(QuotedString, quoted_representer, Dumper=yaml.SafeDumper) test_dict={} with open("sample-string.yaml", "r", encoding="utf-8") as stream: try: # 显式指定SafeLoader,避免默认Loader加载不可信文件时的任意代码执行风险 f = yaml.load(stream, Loader=yaml.SafeLoader) test_dict['data'] = f['data'] except yaml.YAMLError as exc: print(exc) with io.open("output.yaml", "a",encoding="utf-8") as wf: try: yaml.dump(test_dict['data'],wf,allow_unicode=True,default_flow_style=False, Dumper=yaml.SafeDumper) except yaml.YAMLError as exc: print(exc)
注意:原生PyYAML的
yaml.load()不指定Loader的写法存在严重安全漏洞,加载不可信来源的YAML文件会触发任意代码执行,所有场景下都应该显式指定SafeLoader使用。
内容的提问来源于stack exchange,提问作者Vasu Mistry
相关产品推荐
相关产品推荐

