2

我正在使用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(或 JSON,如果 YAML 不起作用......)而不是 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 对象并以原始形式读回它?

4

1 回答 1

0

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


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

有趣的是,您不能选择更易读的协议级别 0(Python 3 中的默认协议是协议版本 3):

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

TypeError:定义了但没有定义getstate的类不能腌制

这是因为RangeDict模块/类有点简约,如果您尝试这样做,它也会显示(或者不显示):

print(rngdict)

只会打印{}

您可能使用了 PyYAMLdump()例程(及其对应的不安全的load())。尽管这可以转储通用 Python 类,但您必须意识到它是在 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(免责声明:我是那个包的作者),所以“所有”你需要做的就是子类RangeDict来提供合适的表示器和构造器(类)方法:

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本身(即不能子类,因为您使用了一些硬编码的库),那么您可以通过嫁接/猴子补丁直接RangeDict添加属性和方法。MyRangeDictRangeDict

于 2017-10-03T06:58:15.193 回答