首页 > 解决方案 > 如何使用 ruamel.yaml 将字典与基于 defaultdict 的字典打印为 yaml 文件?

问题描述

请参考下面显示的这个简单的代码块。我的目标是使用 defaultdict 来制作一个相对简单的字典,并将结果进一步打印为 yaml 文件。

当我手动定义字典时,它似乎工作得很好,并且 YAML 完全按照我想要的方式显示,但是当我使用defaultdict字典时,我收到一条错误消息,不幸的是我无法破译.

当我将字典打印为 JSON 时,它会打印完全相同的输出。我错过了什么?

import sys,ruamel.yaml
import json
from collections import defaultdict

def dict_maker():
    return defaultdict(dict_maker)

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)

错误信息

raise RepresenterError("cannot represent an object: %s" % data)
ruamel.yaml.representer.RepresenterError: cannot represent an object: defaultdict(<function dict_maker at 0x7f1253725a28>, {'beats': defaultdict(<function dict_maker at 0x7f1253725a28>, {'name': u'someapp', 'udp_address': u'239.1.1.1:10101'})})

标签: ruamel.yaml

解决方案


在引用 Python 时,您似乎使用了“字典”一词dict。然而,没有“基于默认字典的字典”之类的东西,这意味着foo之后

foo = dict_maker()

将是 a dict,当然不是:foois a defaultdictwhich is dictbased(即与您所写的完全相反)。

JSON 转储这个并不奇怪,因为它只能愚蠢地转储键值对,就好像它是一个dict. 但是当您尝试重新加载该 JSON 时,您会发现这是多么无用,您无法继续使用它(至少不是以预期的方式):

import sys
import json
from collections import defaultdict
import io

def dict_maker():
    return defaultdict(dict_maker)

app = "someapp"

foo = dict_maker()
foo["beats"]["name"] = app
foo["beats"]["udp_address"] = "239.1.1.1:10101"
io = io.StringIO()
json.dump(foo, io, indent=4)
io.seek(0)
bar = json.load(io)
bar['otherapp']['name'] = 'some_alt_app'
print(bar['beats']['udp_address'])

以上抛出:KeyError: 'otherapp'. 那是因为 JSON 没有保留所有需要的信息。

但是,如果您使用不安全的 YAML 转储程序,则ruamel.yaml可以很好地转储和加载:

import sys
from ruamel.yaml import YAML
from collections import defaultdict
import io

def dict_maker():
    return defaultdict(dict_maker)

app = "someapp"

yaml = YAML(typ='unsafe')
foo = dict_maker()
foo["beats"]["name"] = app
foo["beats"]["udp_address"] = "239.1.1.1:10101"
io = io.StringIO()
yaml.dump(foo, io)
io.seek(0)
print(io.getvalue())
bar = yaml.load(io)
bar['otherapp']['name'] = 'some_alt_app'
print(bar['beats']['udp_address'])

这不会引发错误,就像bar它默认的函数一样defaultdictdict_maker以上印刷品

239.1.1.1:10101

如您所料。

RoundTripDumper/Loader不支持这种开箱即用,是因为它基于,SafeDumper/Loader它不能转储/加载任意 Python 实例,如defaultdict及其dict_maker函数引用。启用它会使加载不安全。

因此,如果您需要使用,RoundTripDumper您应该为其添加一个代表defaultdict或其子类(也可能添加一个代表dict_maker)。为了能够加载它,您还需要构造函数。文档中描述了如何做到这一点(转储 Python 类


推荐阅读