Python zipfile + os.pipe()探索记

2018-02-27 11:25:50来源:oschina作者:谷永权人点击

分享

背景:需要使用Python把多个文件存为zip并通过HTTP上传压缩后的zip文件,不需要压缩,直接zip存储就可以了。


这能有多难,zipfile+requests就搞定了。 唯一的小问题是,先压缩成一个本地文件然后上传显然不太优雅,用IO管道流能一边存储为zip一边上传最好了。 想来也不是什么大问题,用Java的话分分钟搞定,只是Python不太熟。


花上几分钟看看Python的os.pipe(),代码很快撸出来了。执行,文件上传成功。只是,解压报错说zip文件不合法。


为啥了? 上传过程中出错?改改代码不上传数据,直接保存为本地文件,解压依然报错。上传过程中出错的可能性排除。 不通过os.pipe直接压缩到本地文件,解压成功。加上os.pipe中转一下就报错。看来就是这个环节错误了。


对比一下错误的文件和正确的文件,发现文件头部就存在明显差异。


瞄一下zipfile源代码,下面两行行引起了注意:


self._fileobj.seek(self._zinfo.header_offset)
self._fileobj.write(self._zinfo.FileHeader(self._zip64))

seek然后写入文件头信息,可是我这是管道流啊,肯定没法seek回去修改数据的。


为什么要seek回去写文件头了?而且之前用java做过类似的事情,没遇到这问题。 带着疑问去看zip文件格式,原来是文件头信息里的crc校验码和压缩后大小需要seek回去写。不过也说了这两个东西是可以写到后面去的。 但是zipfile并没有提供这样的选择设置。


怎么办了? 把zipfile源代码复制一下改改?太粗暴了。 先看看能不能在传给zipfile的io流对象上做手脚,看能不能重写seek和write方法,然后修改zip头里的标志位改了,把crc校验码写到文件后面去。


那先看看压缩过程中有多少地方调用了seek。在Python标准库的IO流对象seek方法下断点,死活不命中。可能是因为这些标准库实现里使用了大量高级Python特性,导致类定义里的方法其实根本只是一个签名,真正的实现根本不在类定义里。Python这点真是,挺讨厌的。


那自己定义一个IO流对象,手写seek方法下断点好了。 果然命中,还有惊喜。 原来zipfile里会判断流对象是否能seek,不能的话就会把crc校验码那些东西写到后面去,这样就不用seek了。


这就简单啦,写个wrapper包装一下pipe流,直接在seek方法被调用的时候抛出一个AttributeError就好了。利用Python那些有点讨厌的高级特性做这事倒是比java简单多了,重写__getattribute__就可以了。


事实证明我还是想太简单,文件头是对了。文件结尾不对。 继续对照zip文件格式看不对的字节,错误的字节是Central Directory的开始位置偏移。


既然是文件末尾不对,那应该是close的时候写入的数据错误,直觉应该是zipfile里计算字节数错误。 在zipfile源代码的close方法下断点,启动调试,然后想起zipfile里判断io流是否能seek的代码附近还会判断io流是否能tell的。 不会是管道流的tell方法实现也不一样吧,直接tell方法也重写抛出AttributeError得了。 嘿,还真可以了。

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台