我正在编写一个持久存储到磁盘的映射类。我目前只允许str
个密钥,但如果我可以使用更多类型的话会很好:希望可以使用任何可以清除的东西(即与内置dict
相同的要求),但更合理我会接受这些类型的字符串,unicode,int和元组。
为此,我想得出一个确定性的序列化方案。
我首先想到的是使用pickle(或cPickle)模块来序列化密钥,但我注意到pickle
和cPickle
的输出彼此不匹配:
>>> import pickle
>>> import cPickle
>>> def dumps(x):
... print repr(pickle.dumps(x))
... print repr(cPickle.dumps(x))
...
>>> dumps(1)
'I1\n.'
'I1\n.'
>>> dumps('hello')
"S'hello'\np0\n."
"S'hello'\np1\n."
>>> dumps((1, 2, 'hello'))
"(I1\nI2\nS'hello'\np0\ntp1\n."
"(I1\nI2\nS'hello'\np1\ntp2\n."
是否有pickle
的任何实现/协议组合对某些类型的集合是确定性的(例如,只能将cPickle
与协议0一起使用)?
另一个选择是使用repr
转储和ast.literal_eval
加载。我编写了一个函数来确定给定的密钥是否能够在这个过程中存活(它允许的类型相当保守):
def is_reprable_key(key):
return type(key) in (int, str, unicode) or (type(key) == tuple and all(
is_reprable_key(x) for x in key))
此方法的问题是repr
本身对于我在此允许的类型是否具有确定性。我相信由于str / unicode文字的改变,这将无法在2/3版本障碍中存活。这对于2**32 - 1 < x < 2**64
在32位和64位平台之间跳转的整数也不起作用。是否还有其他条件(即在同一个解释器中不同条件下字符串序列化不同)? 编辑:我只是想了解这种情况发生的情况,而不一定要克服它们。
另一个可能过度的选择是编写我自己的repr
,这会使我知道(或怀疑可能是)问题的repr变平。我刚刚在这里写了一个例子:http://gist.github.com/423945
(如果这一切都失败了,那么我可以存储密钥的散列以及密钥和值的pickle,然后遍历具有匹配散列的行,寻找一个对预期密钥进行unpickles的行,但是确实让其他一些事情变得复杂,我宁愿不这样做。编辑: it turns out内置hash
也不是跨平台的确定性。抓点。)
任何见解?
答案 0 :(得分:3)
重要提示:如果在您尝试序列化的对象中嵌入了字典或集类型,则repr()
不具有确定性。可以按任何顺序打印密钥。
例如,print repr({'a':1, 'b':2})
可能打印为{'a':1, 'b':2}
或{'b':2, 'a':1}
,具体取决于Python如何决定管理字典中的键。
答案 1 :(得分:2)
在阅读了大部分源代码(CPython 2.6.5)以实现基本类型的repr之后,我已经得出结论(有合理的信心),这些类型的repr
实际上是确定性的。但是,坦率地说,这是预期的。
我认为repr
方法很容易受到marshal
方法崩溃的几乎所有相同情况的影响(long
s&gt; 2 ** 32永远不会32位计算机上的int
,不保证不会在版本或口译员之间进行更改等。)
我暂时的解决方案是使用repr
方法并编写一个全面的测试套件,以确保repr
在我正在使用的各种平台上返回相同的值。
从长远来看,自定义repr功能会使所有平台/实现差异变得平淡,但对于手头的项目来说肯定是过度的。不过,我可能会在将来这样做。
答案 2 :(得分:1)
“任何值是内置字典的可接受键”是不可行的:这些值包括未定义__hash__
或比较的类的任意实例,隐式使用其id
进行散列和比较的目的,即使在同一个程序的运行中id
也不会相同(除非这些运行在所有方面都严格相同,这是非常棘手的 - 相同的输入,相同的开始时代,完全相同的环境等等。)
对于其项目都是这些类型的字符串,unicodes,int和元组(包括嵌套元组),marshal模块可以提供帮助(在单个版本的Python中:编组代码可以并且确实在不同版本之间进行更改)。 E.g:
>>> marshal.dumps(23)
'i\x17\x00\x00\x00'
>>> marshal.dumps('23')
't\x02\x00\x00\x0023'
>>> marshal.dumps(u'23')
'u\x02\x00\x00\x0023'
>>> marshal.dumps((23,))
'(\x01\x00\x00\x00i\x17\x00\x00\x00'
这是Python 2; Python 3将是类似的(除了这些字节字符串的所有表示都有一个前导b
,但这是一个美化问题,当然u'23'
变成无效语法,'23'
变成了Unicode字符串)。您可以看到一般概念:前导字节表示类型,例如Unicode字符串为u
,整数为i
,元组为(
;然后对于容器来说(作为小端整数)项目本身所遵循的项目数,并将整数序列化为小端形式。 marshal
旨在跨平台移植(对于给定版本;不跨版本),因为它用作已编译字节码文件(.pyc
或.pyo
)中的基础序列化。
答案 3 :(得分:0)
你在段落中提到了一些要求,我想你可能希望对这些有所了解。到目前为止,我收集了:
那么,您是否正在尝试构建一个通用的“这可以做到这一切”的解决方案,或者只是尝试解决当前项目中的直接问题?你应该花一点时间来提出一套明确的要求。
使用哈希看起来对我来说似乎是最好的解决方案,但是你抱怨说你将拥有多个具有相同哈希值的行,这意味着你将存储足够的值来担心哈希值。