Pregunta

Estoy interesado en comprimir datos usando el módulo gzip de Python. Sucede que quiero que la salida comprimida sea determinista, porque a menudo es una propiedad realmente conveniente para las cosas en general, si algún proceso que no sea compatible con gzip buscará cambios en la salida, por ejemplo, o si la salida se firmará criptográficamente.

Desafortunadamente, el resultado es diferente cada vez. Por lo que puedo decir, la única razón para esto es el campo de marca de tiempo en el encabezado gzip, que el módulo Python siempre completa con la hora actual. No creo que se te permita tener una transmisión gzip sin una marca de tiempo, lo cual es una lástima.

En cualquier caso, no parece haber una forma para que la persona que llama del módulo gzip de Python proporcione el tiempo de modificación correcto de los datos subyacentes. (El programa real gzip parece usar la marca de tiempo del archivo de entrada cuando es posible). Me imagino que esto se debe a que, básicamente, lo único que le importa es la marca de tiempo gunzip comando cuando escribo en un archivo, y ahora yo, porque quiero un resultado determinista. ¿Es tanto pedir?

¿Alguien más ha encontrado este problema?

¿Cuál es la forma menos terrible de gzip algunos datos con una marca de tiempo arbitraria de Python?

¿Fue útil?

Solución

Desde Python 2.7 en adelante, puede especificar el tiempo que se utilizará en el encabezado gzip. nótese bien nombre de archivo también se incluye en el encabezado y también se puede especificar manualmente.

import gzip

content = b"Some content"
f = open("/tmp/f.gz", "wb")
gz = gzip.GzipFile(fileobj=f,mode="wb",filename="",mtime=0)
gz.write(content)
gz.close()
f.close()

Otros consejos

Sí, no tienes ninguna opción bonita. El tiempo se escribe con esta línea en _write_gzip_header:

write32u(self.fileobj, long(time.time()))

Dado que no le dan una forma de anular el tiempo, puede hacer una de estas cosas:

  1. Derive una clase de GzipFile y copie la función _write_gzip_header en su clase derivada, pero con un valor diferente en esta línea.
  2. Después de importar el módulo gzip, asigne un nuevo código a su miembro de tiempo. Básicamente, proporcionará una nueva definición del tiempo de nombre en el código gzip, para que pueda cambiar lo que significa time.time ().
  3. Copie todo el módulo gzip, asígnele el nombre my_stable_gzip y cambie la línea que necesita.
  4. Pase un objeto CStringIO como fileobj, y modifique el bytestream después de que gzip haya terminado.
  5. Escriba un objeto de archivo falso que realice un seguimiento de los bytes escritos y pase todo a un archivo real, excepto los bytes de la marca de tiempo, que usted mismo escribe.

Aquí hay un ejemplo de la opción # 2 (sin probar):

class FakeTime:
    def time(self):
        return 1225856967.109

import gzip
gzip.time = FakeTime()

# Now call gzip, it will think time doesn't change!

La opción # 5 puede ser la más limpia en términos de no depender de las partes internas del módulo gzip (no probado):

class GzipTimeFixingFile:
    def __init__(self, realfile):
        self.realfile = realfile
        self.pos = 0

    def write(self, bytes):
        if self.pos == 4 and len(bytes) == 4:
            self.realfile.write("XYZY")  # Fake time goes here.
        else:
            self.realfile.write(bytes)
        self.pos += len(bytes)

Envíe un parche en el que se calcula el cálculo de la marca de tiempo. Es casi seguro que será aceptado.

Tomé el consejo del Sr. Coventry y envió un parche . Sin embargo, dado el estado actual del programa de lanzamiento de Python, con 3.0 a la vuelta de la esquina, no espero que aparezca en un lanzamiento en el corto plazo. ¡Aún así, veremos qué sucede!

Mientras tanto, me gusta la opción 5 del Sr. Batchelder de canalizar la secuencia gzip a través de un pequeño filtro personalizado que establece el campo de marca de tiempo correctamente. Suena como el enfoque más limpio. Como lo demuestra, el código requerido es bastante pequeño, aunque su ejemplo depende de su simplicidad en el supuesto (actualmente válido) de que la implementación del módulo gzip elegirá escribir la marca de tiempo usando exactamente uno llamada de cuatro bytes a write () . Aún así, no creo que sea muy difícil encontrar una versión totalmente general si es necesario.

El enfoque de parches de mono (también conocido como opción 2) es bastante tentador por su simplicidad, pero me da una pausa porque estoy escribiendo una biblioteca que llama gzip , no solo un programa independiente, y parece para mí que alguien podría intentar llamar a gzip desde otro hilo antes de que mi módulo esté listo para revertir su cambio al estado global del módulo gzip . ¡Esto sería especialmente desafortunado si el otro hilo intentara hacer un truco similar de parches de mono! ¡Admito que este problema potencial no parece muy probable que surja en la práctica, pero imagínese lo doloroso que sería diagnosticar tal desorden!

Me puedo imaginar vagamente tratando de hacer algo complicado y complicado y tal vez no tan a prueba de futuro para importar de alguna manera una copia privada del módulo gzip y el parche de mono que , pero en ese punto un filtro parece más simple y más directo.

En lib / gzip.py, encontramos el método que construye el encabezado, incluida la parte que de hecho contiene una marca de tiempo. En Python 2.5, esto comienza en la línea 143:

def _write_gzip_header(self):
    self.fileobj.write('\037\213')             # magic header
    self.fileobj.write('\010')                 # compression method
    fname = self.filename[:-3]
    flags = 0
    if fname:
        flags = FNAME
    self.fileobj.write(chr(flags))
    write32u(self.fileobj, long(time.time())) # The current time!
    self.fileobj.write('\002')
    self.fileobj.write('\377')
    if fname:
        self.fileobj.write(fname + '\000')

Como puede ver, utiliza time.time () para recuperar la hora actual. Según los documentos del módulo en línea, time.time devolverá el tiempo como un número de coma flotante expresado en segundos desde la época, en UTC. Entonces, si cambia esto a una constante de punto flotante de su elección, siempre puede tener los mismos encabezados escritos. No puedo ver una mejor manera de hacer esto a menos que quieras hackear la biblioteca un poco más para aceptar un parámetro de tiempo opcional que usas mientras se configura de manera predeterminada time.time () cuando no está especificado, en cuyo caso, estoy seguro ¡les encantaría que enviaras un parche!

No es bonito, pero podría hacer un parche en el tiempo.tiempo temporalmente con algo como esto:

import time

def fake_time():
  return 100000000.0

def do_gzip(content):
    orig_time = time.time
    time.time = fake_time
    # result = do gzip stuff here
    time.time = orig_time
    return result

No es bonito, pero probablemente funcionaría.

Similar a la respuesta de dominic anterior, pero para un archivo existente:

with open('test_zip1', 'rb') as f_in, open('test_zip1.gz', 'wb') as f_out:
    with gzip.GzipFile(fileobj=f_out, mode='wb', filename="", mtime=0) as gz_out:
         shutil.copyfileobj(f_in, gz_out)

Prueba de sumas MD5:

md5sum test_zip*
7e544bc6827232f67ff5508c8d6c30b3  test_zip1
75decc5768bdc3c98d6e598dea85e39b  test_zip1.gz
7e544bc6827232f67ff5508c8d6c30b3  test_zip2
75decc5768bdc3c98d6e598dea85e39b  test_zip2.gz
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top