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
添加属性和方法。MyRangeDict
RangeDict