2010-02-03 11 views
20

Estoy tratando de encontrar la mejor manera de comprimir una secuencia con Python's zlib.Python: ¿Cómo crear un archivo gzip'd en tiempo real?

Tengo una corriente de tipo fichero de entrada (input, abajo) y una función de salida que acepta un tipo fichero (output_function, más adelante):

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

Y me gustaría gzip- comprimir input trozos antes de enviarlos a output_function:

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

parece que el módulo gzip asume que la entrada o la salida será un archivo en el disco gzip ... Así que supongo que laEl móduloes lo que quiero.

Sin embargo, no ofrece de forma nativa una forma simple de crear un archivo de flujo como ... Y la compresión de flujo que admite viene agregando datos manualmente a un búfer de compresión y luego vaciando ese búfer.

Por supuesto, podría escribir una envoltura alrededor de zlib.Compress.compress y zlib.Compress.flush (Compress se devuelve por zlib.compressobj()), pero estaría preocupado por conseguir búfer tamaños incorrectos, o algo similar.

Entonces, ¿cuál es la forma más sencilla de crear un archivo comprimido gzip-como archivo comprimido con Python?

Editar: Para aclarar, el flujo de entrada y el flujo de salida comprimido son a la vez demasiado grande para caber en la memoria, así que algo como output_function(StringIO(zlib.compress(input.read()))) en realidad no resuelve el problema.

+0

He encontrado una implementación de lo opuesto, un archivo como descomprimir una secuencia gzip'd en effbot: http://effbot.org/librarybook/zlib.htm ... Pero estoy buscando por el contrario (aunque supongo que podría ser útil si tengo que escribir el mío) –

Respuesta

10

Es bastante kludgy (auto referenciación, etc; sólo hay que poner una unos minutos escribiéndolo, nada realmente elegante), pero hace lo que usted desea si todavía está interesado en usar gzip en lugar de zlib directamente.

Básicamente, es una (muy limitado) de tipo fichero objeto que produce un archivo comprimido con gzip cabo de un determinado iterable (por ejemplo, un objeto de tipo fichero, una lista de cadenas, cualquier generador ...)

Por supuesto, produce binarios, por lo que no tenía sentido implementar "readline".

Debería poder expandirlo para cubrir otros casos o para ser utilizado como un objeto iterable.

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() 
+4

haha ​​muy inteligente - pasando 'self' al GzipFile. ¡Me gusta! –

+1

(bien, entonces veo su punto de que no es particularmente elegante pasar 'self' al GzipFile ... Pero todavía creo que es un hack limpio). –

+0

He corregido un pequeño error en el código. Al leer con tamaño <0, no borró el búfer. No creo que lo vayas a usar así, pero un error es un error ... O :) –

4

El módulo gzip admite la compresión de un objeto similar a un archivo, pasa un parámetro fileobj a GzipFile, así como un nombre de archivo. No es necesario que exista el nombre de archivo que ingresa, pero el encabezado gzip tiene un campo de nombre de archivo que debe completarse.

actualización

Esta respuesta no funciona. Ejemplo:

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

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

salida:

===> 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 
+0

Mmmm ... No me había dado cuenta de eso ... Pero no estoy seguro de que funcione: o bien el 'fileobj' debe ser un gzip'd flujo de entrada, o un flujo de salida en el que se escribirán los datos gzip'd. Entonces, mejor que nada, pero todavía no es lo que me gustaría. –

+1

Explique por qué esto no resuelve su problema. Yo uso 'fd = gzip.GzipFile (fileobj = fd)' y funciona como debería. – guettli

+0

@guettli el autor espera que el objeto 'fd' no tenga el método' seek'. –

2

Utilice la cStringIO (o StringIO) módulo en conjunción con 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

El problema con esto, sin embargo, es que todo el flujo de entrada debe cargarse en la memoria (cuando se pasa a 'zlib.compress') y luego debe cargarse en la memoria * nuevamente * cuando se devuelve desde' zlib.decompress' . –

+1

Nunca deja memoria, si usa StringIO. En su pregunta, dijo que quería un "objeto similar a un archivo", que es la terminología común de Python para un objeto que tiene métodos similares a un objeto de archivo. No dice nada sobre si vive en el disco o no. Pero luego también sugirió que no quería un archivo gz. ¿Puede por favor ser más claro sobre lo que realmente está buscando? – jcdyer

+0

Err, lo siento, sí, eso es mi culpa. En mi opinión, "objeto similar a un archivo" implica "algo destinado a ser procesado en fragmentos", pero supongo que es una suposición errónea. He actualizado la pregunta. –

6

Aquí hay una versión más limpia, sin autorreferencia, basada en la respuesta muy útil de Ricardo 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) 

Ventajas:

  • evita repiten la concatenación de cadenas, lo que causaría toda la cadena que se copia repetidamente.
  • Lee un tamaño de CHUNK fijo de la secuencia de entrada, en lugar de leer líneas completas a la vez (que puede ser arbitrariamente larga).
  • Evita las referencias circulares.
  • Evita el método de "escritura" público engañoso de GzipCompressStream(), que en realidad solo se usa internamente.
  • Aprovecha el cambio de nombre para variables internas de miembros.
+0

No funciona en Python 3, error bytes/str. –

Cuestiones relacionadas