Python:创建流式gzip文件?

时间:2010-02-03 14:19:14

标签: python gzip zlib

我正在尝试找出使用Python zlib压缩流的最佳方法。

我有一个类似文件的输入流(下面是input)和一个接受类文件(output_function的输出函数):

with open("file") as input:
    output_function(input)

我希望在将input发送给output_function之前先压缩with open("file") as input: output_function(gzip_stream(input)) 块:

zlib.Compress.compress

看起来gzip模块假定输入或输出都是gzip的磁盘文件......所以我假设zlib模块就是我想要的。< / p>

但是,它本身并没有提供一种简单的方法来创建类似于文件的流...而且它支持的流压缩是通过手动将数据添加到压缩缓冲区,然后刷新缓冲区来实现的。

当然,我可以在zlib.Compress.flushCompress之间编写一个包装器(zlib.compressobj()返回output_function(StringIO(zlib.compress(input.read())))),但我会担心缓冲区大小错误或类似的东西。

那么,使用Python创建流式,gzip压缩文件的最简单方法是什么?

编辑:为了澄清,输入流和压缩输出流都太大而无法容纳在内存中,因此像{{1}}这样的东西并没有真正解决问题。

5 个答案:

答案 0 :(得分:10)

它非常笨拙(自我引用等;只需花几分钟写一下,没有什么真正优雅的),但如果您仍然对使用gzip而不是{{1}感兴趣的话,它会做您想要的直接。

基本上,zlib是一个(非常有限的)类文件对象,它从给定的可迭代中生成一个gzip压缩文件(例如,类文件对象,字符串列表,任何生成器...)

当然,它产生二进制,因此实现“readline”没有任何意义。

您应该能够将其展开以涵盖其他情况或将其用作可迭代对象本身。

GzipWrap

答案 1 :(得分:7)

这是一个基于RicardoCárdenes非常有用的答案的更清洁,非自我引用的版本。

from gzip import GzipFile
from collections import deque


CHUNK = 16 * 1024


class Buffer (object):
    def __init__ (self):
        self.__buf = deque()
        self.__size = 0
    def __len__ (self):
        return self.__size
    def write (self, data):
        self.__buf.append(data)
        self.__size += len(data)
    def read (self, size=-1):
        if size < 0: size = self.__size
        ret_list = []
        while size > 0 and len(self.__buf):
            s = self.__buf.popleft()
            size -= len(s)
            ret_list.append(s)
        if size < 0:
            ret_list[-1], remainder = ret_list[-1][:size], ret_list[-1][size:]
            self.__buf.appendleft(remainder)
        ret = ''.join(ret_list)
        self.__size -= len(ret)
        return ret
    def flush (self):
        pass
    def close (self):
        pass


class GzipCompressReadStream (object):
    def __init__ (self, fileobj):
        self.__input = fileobj
        self.__buf = Buffer()
        self.__gzip = GzipFile(None, mode='wb', fileobj=self.__buf)
    def read (self, size=-1):
        while size < 0 or len(self.__buf) < size:
            s = self.__input.read(CHUNK)
            if not s:
                self.__gzip.close()
                break
            self.__gzip.write(s)
        return self.__buf.read(size)

优点:

  • 避免重复的字符串连接,这会导致重复复制整个字符串。
  • 从输入流中读取固定的CHUNK大小,而不是一次读取整行(可以任意长)。
  • 避免使用循环引用。
  • 避免误导GzipCompressStream()的公开“写”方法,该方法实际上只在内部使用。
  • 利用名称修改内部成员变量。

答案 2 :(得分:4)

gzip模块支持压缩到类文件对象,将fileobj参数传递给GzipFile,以及文件名。您传入的文件名不需要存在,但gzip标头有一个需要填写的文件名字段。

<强>更新

这个答案不起作用。例如:

# tmp/try-gzip.py 
import sys
import gzip

fd=gzip.GzipFile(fileobj=sys.stdin)
sys.stdout.write(fd.read())

输出:

===> cat .bash_history  | python tmp/try-gzip.py  > tmp/history.gzip
Traceback (most recent call last):
  File "tmp/try-gzip.py", line 7, in <module>
    sys.stdout.write(fd.read())
  File "/usr/lib/python2.7/gzip.py", line 254, in read
    self._read(readsize)
  File "/usr/lib/python2.7/gzip.py", line 288, in _read
    pos = self.fileobj.tell()   # Save current position
IOError: [Errno 29] Illegal seek

答案 3 :(得分:2)

将cStringIO(或StringIO)模块与zlib结合使用:

>>> import zlib
>>> from cStringIO import StringIO
>>> s.write(zlib.compress("I'm a lumberjack"))
>>> s.seek(0)
>>> zlib.decompress(s.read())
"I'm a lumberjack"

答案 4 :(得分:1)

这有效(至少在python 3中有效):

with s3.open(path, 'wb') as f:
    gz = gzip.GzipFile(filename, 'wb', 9, f)
    gz.write(b'hello')
    gz.flush()
    gz.close()

此处将其写入带有gzip压缩的s3fs的文件对象。 魔术是f参数,它是GzipFile的fileobj。您必须提供gzip标头的文件名。