【问题标题】:setting the gzip timestamp from Python从 Python 设置 gzip 时间戳
【发布时间】:2008-11-05 02:50:59
【问题描述】:

我对使用 Python 的 gzip 模块压缩数据很感兴趣。碰巧我希望压缩输出是确定性的,因为这对于一般事物来说通常是一个非常方便的属性——例如,如果某个非 gzip 感知进程正在寻找输出中的变化,或者如果输出将被加密签名。

很遗憾,每次输出都不一样。据我所知,唯一的原因是 gzip 标头中的时间戳字段,Python 模块始终使用当前时间填充该字段。我认为实际上不允许您使用没有时间戳的 gzip 流,这太糟糕了。

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

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

从 Python 中gzip 一些带有任意时间戳的数据最不可怕的方法是什么?

【问题讨论】:

    标签: python gzip


    【解决方案1】:

    是的,你没有任何漂亮的选择。时间用_write_gzip_header中的这一行写:

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

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

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

    这是选项 #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)
    

    【讨论】:

    • 解决方案 #2 对我有用。 1225856967.109 是如何被选中的?这是否与 gzip --no-name 或 python 的 mtime=0 将返回的字节相同?如果所有覆盖时间的实现都使用相同的值,那就太好了。
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      提交一个patch,其中计算了时间戳。它几乎肯定会被接受。

      【讨论】:

      • 我无法想象补丁会在 Ubuntu(我碰巧在使用)中出现相当长的一段时间,这意味着我仍然需要一个解决方法。不过,我认为这是一个很好的答案!
      【解决方案4】:

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

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

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

      我可以模糊地想象尝试做一些棘手和复杂的事情,也许不是那么面向未来,以某种方式导入 gzip 模块和猴子补丁的私有副本那个,但到那时过滤器似乎更简单、更直接。

      【讨论】:

        【解决方案5】:

        在 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() 时使用的可选时间参数,在这种情况下,我敢肯定如果您提交补丁,他们会很高兴的!

        【讨论】:

          【解决方案6】:

          这不是很漂亮,但你可以用这样的东西临时修补 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
          

          它不漂亮,但它可能会工作。

          【讨论】:

          • 我对这种方法的主要反对意见是我正在编写一个库,并且我的库的调用者可能试图在另一个线程中使用 gzip,在这种情况下,我所做的更改可能会影响其他线程。如果其他线程尝试使用相同的技巧,这尤其可怕!
          【解决方案7】:

          类似于上面多米尼克的答案,但对于现有文件:

          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
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-12-06
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多