从压缩文件中加载pickle文件

6 投票
1 回答
11247 浏览
提问于 2025-04-15 23:45

出于某种原因,我无法让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 个回答

8

这是因为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,否则不推荐使用)。

撰写回答