在 Python 中使用 YAML 或 JSON 序列化 RangeDict

Serializing a RangeDict using YAML or JSON in Python

我正在使用 RangeDict 制作包含范围的字典。当我使用 Pickle 时,它​​很容易写入文件并稍后读取。

import pickle
from rangedict import RangeDict

rngdct = RangeDict()
rngdct[(1, 9)] = \
    {"Type": "A", "Series": "1"}
rngdct[(10, 19)] = \
    {"Type": "B", "Series": "1"}

with open('rangedict.pickle', 'wb') as f:
    pickle.dump(rngdct, f)

但是,我想使用 YAML(如果 YAML 不起作用,则 JSON...)而不是 Pickle,因为大多数人似乎都讨厌它(而且我想要人类可读的文件,这样他们对阅读它们的人有意义)

基本上,更改代码以调用 yaml 并在 'w' 模式下打开文件,而不是在 'wb' 下对写作方面有帮助,但是当我在另一个脚本中读取文件时,我收到这些错误:

File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/yaml/constructor.py", line 129, in construct_mapping
value = self.construct_object(value_node, deep=deep)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/yaml/constructor.py", line 61, in construct_object
"found unconstructable recursive node", node.start_mark)
yaml.constructor.ConstructorError: found unconstructable recursive node

我迷路了。如何序列化 rangedict 对象并以其原始形式读回?

TL;DR;跳到此答案的底部以获取工作代码


我敢肯定有些人讨厌 pickle,它在重构代码时肯定会让人头疼(当 pickle 对象的 classes 移动到不同的文件时)。但更大的问题是 pickle 是不安全的,只是一个 YAML 是你使用它的方式。

有趣的是,您不能 pickle 到更具可读性的 protocol level 0(Python 3 中的默认协议版本 3)如:

pickle.dump(rngdct, f, 协议=0) 将抛出:

TypeError: a class that defines slots without defining getstate cannot be pickled

这是因为 RangeDict module/class 有点简约,如果您尝试这样做,它也会显示(或更确切地说不显示):

print(rngdict)

这将只打印 {}

您可能使用了 PyYAML dump() 例程(及其相应的不安全 load())。尽管这可以转储通用 Python classes,但您必须意识到它是在 Python 3.0 之前或大致同时实现的。 (并且 Python 3 支持稍后实现)。尽管 YAML 解析器没有理由可以转储和加载 pickle 所做的确切信息,但它不会挂钩到 pickle 支持例程(尽管它可以),当然也不会挂钩到信息中Python 3 个特定的酸洗协议。

无论如何,如果没有 RangeDict 对象的特定表示器(和构造函数),使用 YAML 没有任何意义:它使加载可能不安全,并且您的 YAML 包含 所有使对象高效的血腥细节。如果你这样做 yaml.dump():

!!python/object:rangedict.RangeDict
_root: &id001 !!python/object/new:rangedict.Node
  state: !!python/tuple
  - null
  - color: 0
    left: null
    parent: null
    r: !!python/tuple [1, 9]
    right: !!python/object/new:rangedict.Node
      state: !!python/tuple
      - null
      - color: 1
        left: null
        parent: *id001
        r: !!python/tuple [10, 19]
        right: null
        value: {Series: '1', Type: B}
    value: {Series: '1', Type: A}

IMO 在 YAML 中的 可读 表示是:

!rangedict
[1, 9]:
  Type: A
  Series: '1'
[10, 19]:
  Type: B
  Series: '1'

由于序列用作键,如果不对解析器进行重大修改,PyYAML 无法加载它。但幸运的是,那些修改已经合并到 ruamel.yaml 中(免责声明:我是那个包的作者),所以 "all" 你需要做的是 subclass RangeDict 提供合适的代表和构造函数(class)方法:

import io
import ruamel.yaml
from rangedict import RangeDict

class MyRangeDict(RangeDict):
    yaml_tag = u'!rangedict'

    def _walk(self, cur):
        # walk tree left -> parent -> right
        if cur.left:
            for x in self._walk(cur.left):
                yield x
        yield cur.r
        if cur.right:
            for x in self._walk(cur.right):
                yield x

    @classmethod
    def to_yaml(cls, representer, node):
        d = ruamel.yaml.comments.CommentedMap()
        for x in node._walk(node._root):
            d[ruamel.yaml.comments.CommentedKeySeq(x)] = node[x[0]]
        return representer.represent_mapping(cls.yaml_tag, d)

    @classmethod
    def from_yaml(cls, constructor, node):
        d = cls()
        for x, y in node.value:
            x = constructor.construct_object(x, deep=True)
            y = constructor.construct_object(y, deep=True)
            d[x] = y
        return d


rngdct = MyRangeDict()
rngdct[(1, 9)] = \
    {"Type": "A", "Series": "1"}
rngdct[(10, 19)] = \
    {"Type": "B", "Series": "1"}

yaml = ruamel.yaml.YAML()
yaml.register_class(MyRangeDict)  # tell the yaml instance about this class

buf = io.StringIO()

yaml.dump(rngdct, buf)
data = yaml.load(buf.getvalue())

# test for round-trip equivalence:
for x in data._walk(data._root):
    for y in range(x[0], x[1]+1):
        assert data[y]['Type'] == rngdct[y]['Type']
        assert data[y]['Series'] == rngdct[y]['Series']

buf.getvalue() 正是之前显示的可读表示。

如果你必须处理转储 RangeDict 本身(即不能 subclass 因为你使用了一些具有 RangeDict 硬编码的库),那么你可以添加属性和方法MyRangeDict 的 grafting/monkeypatching.

直接到 RangeDict