从Python设置gzip时间戳

时间:2008-11-05 02:51:00

标签: python gzip

我对使用Python的gzip模块压缩数据感兴趣。碰巧我希望压缩输出是确定性的,因为这通常是一般非常方便的属性 - 如果某些非gzip感知的进程要在输出中寻找变化,比如说,或者如果输出将以加密方式签名。

不幸的是,每次输出都不同。据我所知,唯一的原因是gzip头中的timestamp字段,Python模块总是填充当前时间。我认为你实际上不允许你有一个没有时间戳的gzip流,这太糟糕了。

在任何情况下,Python的gzip模块的调用者似乎都没有办法提供底层数据的正确修改时间。 (实际的gzip程序似乎尽可能使用输入文件的时间戳。)我想这是因为基本上唯一关心时间戳的是写入文件时gunzip命令 - 现在,我,因为我想要确定性输出。有这么多要问吗?

还有其他人遇到过这个问题吗?

使用Python的任意时间戳gzip某些数据的最不可靠的方法是什么?

7 个答案:

答案 0 :(得分:8)

是的,你没有任何漂亮的选择。在_write_gzip_header:

中使用此行写入时间
write32u(self.fileobj, long(time.time()))

由于他们没有给你一种覆盖时间的方法,你可以做以下事情之一:

  1. 从GzipFile派生一个类,并将_write_gzip_header函数复制到派生类中,但在这一行中使用不同的值。
  2. 导入gzip模块后,将新代码分配给其时间成员。您将基本上在gzip代码中提供名称时间的新定义,因此您可以更改time.time()的含义。
  3. 复制整个gzip模块,并将其命名为my_stable_gzip,然后更改所需的行。
  4. 将CStringIO对象作为fileobj传递,并在完成gzip后修改字节流。
  5. 写一个伪文件对象来跟踪写入的字节,并将所有内容传递给一个真实的文件,除了你自己编写的时间戳的字节。
  6. 以下是选项#2(未经测试)的示例:

    class FakeTime:
        def time(self):
            return 1225856967.109
    
    import gzip
    gzip.time = FakeTime()
    
    # Now call gzip, it will think time doesn't change!
    

    选项#5可能是最简洁的,不依赖于gzip模块的内部结构(未经测试):

    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)
    

答案 1 :(得分:5)

从Python 2.7开始,您可以指定在gzip标头中使用的时间。注:文件名也包含在标题中,也可以手动指定。

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

答案 2 :(得分:2)

提交patch,其中计算了时间戳的计算结果。几乎肯定会被接受。

答案 3 :(得分:1)

我接受了考文垂先生的建议和submitted a patch。但是,考虑到Python发布计划的当前状态,即将推出3.0,我不希望它很快就会出现在发布版本中。不过,我们会看到会发生什么!

与此同时,我喜欢Batchelder先生的选项5,即通过一个小的自定义过滤器来管理gzip流,该过滤器可以正确设置时间戳字段。这听起来像是最干净的方法。正如他所演示的那样,所需的代码实际上非常小,尽管他的示例确实依赖于(当前有效的)假设的一些简单性,即gzip模块实现将选择使用恰好一个四字节写入时间戳致电write()。不过,如果需要的话,我认为想出一个完全通用的版本并不是很困难。

猴子修补方法(又称选项2)非常诱人,因为它的简单性让我暂停,因为我正在编写一个调用gzip的库,而不仅仅是一个独立的程序,而且在我看来在模块准备好将其更改转换为gzip模块的全局状态之前,有人可能会尝试从另一个线程调用gzip。如果另一个线程试图拉出类似的猴子修补特技,这将是特别不幸的!我承认这个潜在的问题听起来不太可能在实践中出现,但想象一下诊断这样一团糟是多么痛苦!

我可以模糊地想象一下,尝试做一些棘手而复杂的事情,并且可能不会以某种方式导入gzip模块和猴子补丁 的私有副本,但是通过过滤器看起来更简单,更直接。

答案 4 :(得分:0)

在lib / gzip.py中,我们找到构建头的方法,包括确实包含时间戳的部分。在Python 2.5中,这从第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')

如您所见,它使用time.time()来获取当前时间。根据在线模块文档,time.time将“返回时间作为自纪元以来以秒为单位表示的浮点数,以UTC为单位。”因此,如果将此更改为您选择的浮点常量,则始终可以写出相同的标题。我无法看到更好的方法来执行此操作,除非您想要更多地破解库以接受您使用的可选时间参数,而默认为time.time()时未指定,在这种情况下,我确定如果你提交补丁,他们会喜欢它!

答案 5 :(得分:0)

它不漂亮,但你可以用这样的东西暂时monkeypatch time.time:

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

它不漂亮,但可能会有效。

答案 6 :(得分:0)

与上面的dominic的答案类似,但对于现有的文件:

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)

测试MD5总和:

md5sum test_zip*
7e544bc6827232f67ff5508c8d6c30b3  test_zip1
75decc5768bdc3c98d6e598dea85e39b  test_zip1.gz
7e544bc6827232f67ff5508c8d6c30b3  test_zip2
75decc5768bdc3c98d6e598dea85e39b  test_zip2.gz