Python: Создание потокового файла в формате gzip? - PullRequest
22 голосов
/ 03 февраля 2010

Я пытаюсь найти лучший способ сжать поток с помощью 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))

Похоже, что модуль gzip предполагает, что вход или выход будет gzip'd-файлом-на-диске ... Поэтому я предполагаю, что модуль zlib - это то, что Я хочу.

Однако изначально он не предлагает простой способ создания потокового файла, похожего на… И сжатие потока, которое он поддерживает, происходит путем ручного добавления данных в буфер сжатия, а затем очистки этого буфера.

Конечно, я мог бы написать обертку вокруг zlib.Compress.compress и zlib.Compress.flush (Compress возвращается zlib.compressobj()), но я бы беспокоился о неправильном размере буфера или о чем-то подобном.

Итак, как проще всего создать потоковый файл с сжатием gzip в Python?

Редактировать : Чтобы уточнить, входной поток и сжатый выходной поток слишком велики для размещения в памяти, поэтому что-то вроде output_function(StringIO(zlib.compress(input.read()))) на самом деле не решает проблему.

Ответы [ 5 ]

10 голосов
/ 03 февраля 2010

Это довольно хитроумно (самоссылка и т. Д .; просто потратьте несколько минут на написание, ничего по-настоящему элегантного), но оно делает то, что вам нужно, если вы все еще заинтересованы в использовании gzip вместо zlib напрямую.

По сути, GzipWrap - это (очень ограниченный) подобный файлу объект, который создает сжатый файл из заданного итерируемого объекта (например, подобный файлу объект, список строк, любой генератор ...)

Конечно, он создает двоичный файл, поэтому не было смысла в реализации 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()
7 голосов
/ 22 июля 2015

Вот более чистая, не ссылающаяся на себя версия, основанная на очень полезном ответе Рикардо Карденеса.

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 (), который на самом деле используется только для внутреннего использования.
  • Использует преимущества искажения имени для внутренних переменных-членов.
4 голосов
/ 03 февраля 2010

Модуль gzip поддерживает сжатие в подобный файлу объект, передает параметр fileobj в GzipFile, а также имя файла. Имя файла, которое вы передаете, не должно существовать, но заголовок gzip имеет поле имени файла, которое необходимо заполнить.

Обновление

Этот ответ не работает. Пример:

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

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

выход: * +1010 *

===> 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
2 голосов
/ 03 февраля 2010

Используйте модуль 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"
1 голос
/ 08 мая 2019

Это работает (по крайней мере, в Python 3):

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

Здесь он записывает в файловый объект s3fs сжатие gzip. Магия - это параметр f, который равен fileobj в GzipFile. Вы должны указать имя файла для заголовка gzip.

...