从压缩文件中加载pickle文件
出于某种原因,我无法让cPickle.load在ZipFile.open()返回的文件对象上正常工作。不过,如果我对ZipFile.open()返回的文件对象调用read(),我就可以使用cPickle.loads了。
举个例子 ....
import zipfile
import cPickle
# the data we want to store
some_data = {1: 'one', 2: 'two', 3: 'three'}
#
# create a zipped pickle file
#
zf = zipfile.ZipFile('zipped_pickle.zip', 'w', zipfile.ZIP_DEFLATED)
zf.writestr('data.pkl', cPickle.dumps(some_data))
zf.close()
#
# cPickle.loads works
#
zf = zipfile.ZipFile('zipped_pickle.zip', 'r')
sd1 = cPickle.loads(zf.open('data.pkl').read())
zf.close()
#
# cPickle.load doesn't work
#
zf = zipfile.ZipFile('zipped_pickle.zip', 'r')
sd2 = cPickle.load(zf.open('data.pkl'))
zf.close()
注意:我并不想只压缩一个pickle文件,而是想压缩很多其他类型的文件。这只是一个例子。
1 个回答
这是因为Python的zipfile
模块在实现伪文件对象时存在一些缺陷(这个模块的ZipFile
类的.open
方法是在Python 2.6中引入的)。我们来看一下:
>>> f = zf.open('data.pkl')
>>> f.read(1)
'('
>>> f.readline()
'dp1\n'
>>> f.read(1)
''
>>>
.read(1)
和.readline()
的顺序实际上是.loads
内部所做的(在Python 2中,默认使用的是协议0的pickle,这就是你现在使用的)。不幸的是,zipfile
的缺陷导致这个特定的顺序无法正常工作,第一次读取或读取行后就会出现一个虚假的“文件结束”情况(.read
返回一个空字符串)。
我不太确定这个Python标准库中的bug在Python 2.7中是否修复了——我会去查一下。
编辑:刚刚查了一下——这个bug在Python 2.7 rc1(目前最新的2.7版本的候选发布)中已经修复。我还不知道它是否在最新的2.6的bug修复版本中也修复了。
再次编辑:这个bug在Python 2.6.5中仍然存在,这是Python 2.6的最新bug修复版本——所以如果你不能升级到2.7,并且需要ZipFile.open
中的伪文件对象表现得更好,那么将2.7的修复移植到2.6似乎是唯一可行的解决方案。
需要注意的是,你并不一定需要伪文件对象表现得更好;如果你能控制转储调用,并且可以使用最新的协议,一切都会正常:
>>> zf = zipfile.ZipFile('zipped_pickle.zip', 'w', zipfile.ZIP_DEFLATED)
>>> zf.writestr('data.pkl', cPickle.dumps(some_data, -1))
>>> sd2 = cPickle.load(zf.open('data.pkl'))
>>>
只有旧的、兼容性差的“协议0”(默认使用)在混合使用读取和读取行的调用时,才需要伪文件对象的正确行为(协议0也比较慢,并且生成的pickle文件更大,所以除非你的应用程序必须兼容旧版本的Python,或者必须使用协议0生成的仅限ASCII的pickle,否则不推荐使用)。