Python:创建流式gzip文件?

Dav*_*ver 22 python gzip zlib

我试图找出用Python压缩流的最佳方法zlib.

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

with open("file") as input:
    output_function(input)
Run Code Online (Sandbox Code Playgroud)

我想input在发送之前对gzip压缩块进行压缩output_function:

with open("file") as input:
    output_function(gzip_stream(input))
Run Code Online (Sandbox Code Playgroud)

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

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

当然,我可以写一个包装器zlib.Compress.compresszlib.Compress.flush(Compress由它返回zlib.compressobj()),但我担心缓冲区大小错误或类似的东西.

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

编辑:为了澄清,输入流和压缩输出流都太大output_function(StringIO(zlib.compress(input.read())))而不适合内存,所以类似的东西并没有真正解决问题.

Ric*_*nes 11

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

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

当然,它产生二进制因此在实现"readline"时没有任何意义.

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

from gzip import GzipFile

class GzipWrap(object):
    # input is a filelike object that feeds the input
    def __init__(self, input, filename = None):
        self.input = input
        self.buffer = ''
        self.zipper = GzipFile(filename, mode = 'wb', fileobj = self)

    def read(self, size=-1):
        if (size < 0) or len(self.buffer) < size:
            for s in self.input:
                self.zipper.write(s)
                if size > 0 and len(self.buffer) >= size:
                    self.zipper.flush()
                    break
            else:
                self.zipper.close()
            if size < 0:
                ret = self.buffer
                self.buffer = ''
        else:
            ret, self.buffer = self.buffer[:size], self.buffer[size:]
        return ret

    def flush(self):
        pass

    def write(self, data):
        self.buffer += data

    def close(self):
        self.input.close()
Run Code Online (Sandbox Code Playgroud)

  • 哈哈非常聪明 - 将'self`传递给GzipFile.我喜欢! (4认同)
  • 为什么标准库提供的不是这样的?没有流的gzip实用程序有什么用呢?通常情况是整个文件不适合内存(毕竟它是gzip的原因). (3认同)

Col*_*lin 7

这是一个更清洁,非自我参考的版本,基于里卡多Cá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)
Run Code Online (Sandbox Code Playgroud)

好处:

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


小智 5

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

更新

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

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

fd=gzip.GzipFile(fileobj=sys.stdin)
sys.stdout.write(fd.read())
Run Code Online (Sandbox Code Playgroud)

输出:

===> 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
Run Code Online (Sandbox Code Playgroud)

  • 请解释为什么这不能解决您的问题。我使用 `fd=gzip.GzipFile(fileobj=fd)` 并且它的工作原理如下。 (2认同)