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())))
действительно не решает проблему.
Ответы
Ответ 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.