Подтвердить что ты не робот

Python: Создание потокового gzip'd файла?

Я пытаюсь найти лучший способ сжать поток с помощью Python zlib.

У меня есть входной поток, подобный файлу (input, ниже) и функция вывода, которая принимает файл-подобный (output_function, ниже):

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

И я хотел бы gzip-compress input chunks перед отправкой их в 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()))) действительно не решает проблему.

4b9b3361

Ответ 1

Это довольно kludgy (self referencing и т.д., просто наложите несколько минут на запись, ничего действительно элегантного), но он делает то, что вы хотите, если вы все еще заинтересованы в использовании gzip вместо zlib.

В принципе, GzipWrap является (очень ограниченным) файлоподобным объектом, который создает gzipped файл из заданного итерабельного (например, файл-подобный объект, список строк, любой генератор...)

Конечно, он создает двоичный код, поэтому нет смысла в реализации "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()

Ответ 2

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

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 из входного потока, вместо того, чтобы читать целые строки за раз (что может быть сколь угодно длинным).
  • Предотвращает циклические ссылки.
  • Избегает вводить в заблуждение общедоступный метод "write" GzipCompressStream(), который действительно используется только внутри.
  • Использует использование имени для внутренних переменных-членов.

Ответ 3

Модуль 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

Ответ 4

Используйте модуль 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"

Ответ 5

Это работает (по крайней мере, в 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.