如何用ruamel.yaml将普通字典与defaultdict字典输出为YAML文件?
你遇到的问题核心在于:ruamel.yaml的RoundTripDumper默认不识别collections.defaultdict这种自定义字典类型。虽然JSON序列化工具会自动把defaultdict当作普通dict处理,但ruamel.yaml的RoundTripDumper对类型的校验更严格——它需要明确知道如何序列化非原生的Python对象,遇到没有对应处理逻辑的类型时就会抛出RepresenterError。
为什么JSON可以正常输出?
JSON序列化时,只会遍历defaultdict的键值对,不会保留它的defaultdict类型信息,相当于自动把它降级成了普通字典,所以输出和原生dict完全一致。但ruamel.yaml的RoundTripDumper会检查对象的实际类型,发现是它没有预设处理规则的defaultdict,就会报错。
解决办法
这里有两种实用方案,你可以根据需求选择:
方案1:递归将嵌套defaultdict转为普通dict
因为你的defaultdict是多层嵌套结构(dict_maker生成的是递归的defaultdict),所以需要写一个递归函数,把所有层级的defaultdict都转换成原生dict:
def defaultdict_to_dict(d): if isinstance(d, defaultdict): # 递归处理每个值,把嵌套的defaultdict也转成dict d = {k: defaultdict_to_dict(v) for k, v in d.items()} return d
然后在dump前转换你的foo对象:
print "defaultdict dictionary as a yaml\n" foo_normal = defaultdict_to_dict(foo) ruamel.yaml.dump(foo_normal, sys.stdout, Dumper=ruamel.yaml.RoundTripDumper)
方案2:给ruamel.yaml注册defaultdict的表示器
如果你想继续使用defaultdict的便利性,不想每次手动转换,可以给RoundTripDumper注册一个自定义处理逻辑,让它知道如何序列化defaultdict:
from ruamel.yaml import representer def represent_defaultdict(dumper, data): # 告诉dumper把defaultdict当作普通dict来序列化 return dumper.represent_dict(data.items()) # 给RoundTripDumper注册这个处理逻辑 ruamel.yaml.RoundTripDumper.add_representer(defaultdict, represent_defaultdict)
注册完成后,你就可以直接dumpfoo了,不需要额外转换:
print "defaultdict dictionary as a yaml\n" ruamel.yaml.dump(foo, sys.stdout, Dumper=ruamel.yaml.RoundTripDumper)
完整测试代码(以方案2为例)
把注册逻辑集成到你的代码里,就能正常输出YAML了:
import sys, ruamel.yaml import json from collections import defaultdict from ruamel.yaml import representer def dict_maker(): return defaultdict(dict_maker) # 注册defaultdict的表示器 def represent_defaultdict(dumper, data): return dumper.represent_dict(data.items()) ruamel.yaml.RoundTripDumper.add_representer(defaultdict, represent_defaultdict) S = ruamel.yaml.scalarstring.DoubleQuotedScalarString app = "someapp" d = {'beats':{'name':S(app), 'udp_address':S('239.1.1.1:10101')}} foo = dict_maker() foo["beats"]["name"] = S(app) foo["beats"]["udp_address"] = S("239.1.1.1:10101") print "Regular dictionary" print json.dumps(d, indent=4) print "defaultdict dictionary" print json.dumps(foo, indent=4) print "dictionary as a yaml\n" ruamel.yaml.dump(d, sys.stdout, Dumper=ruamel.yaml.RoundTripDumper) print "defaultdict dictionary as a yaml\n" ruamel.yaml.dump(foo, sys.stdout, Dumper=ruamel.yaml.RoundTripDumper)
运行这段代码,就能得到和普通字典完全一致的YAML输出了。
内容的提问来源于stack exchange,提问作者MediocreTechie

