2008-11-05 70 views
4

我对使用Python的gzip模块压缩数据很感兴趣。恰巧我想要压缩输出是确定性的,因为对于一般情况来说,这通常是非常方便的属性 - 如果某些非gzip感知流程将要查找输出中的变化,或者如果输出将被加密签名。设置Python的gzip时间戳

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

无论如何,Python的gzip模块的调用者似乎没有办法为基础数据提供正确的修改时间。 (实际上gzip程序似乎在可能的情况下使用输入文件的时间戳。)我想这是因为基本上唯一关心时间戳的是写入文件时的命令gunzip - 现在,因为我需要确定性输出。这是不是要求太过分了?

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

gzip什么是最不可怕的方式来自Python的任意时间戳的一些数据?

回答

3

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

是的,我猜这个补丁早就被接受了!时光飞逝...... – zaphod 2016-03-20 05:02:22

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()的时候不指定,在这种情况下,我确信如果您提交了补丁,他们会喜欢它!

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. 编写一个假的文件对象,用于跟踪写入的字节,并将所有内容传递给一个真实文件,除了您自己编写的时间戳的字节外。

下面的选项#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) 
0

它不漂亮,但你可以像这样的东西暂时猴补丁了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 

这不太好,但它可能会奏效。

+0

我主要反对这种做法,我” m编写一个库,并且我的库的调用者可能会尝试在另一个线程中使用gzip,在这种情况下,我所做的更改可能会影响其他线程。如果其他线程试图使用相同的技巧,这是特别可怕的! – zaphod 2008-11-06 00:35:05

2

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

+0

我无法想象这个补丁会在Ubuntu中出现(我恰好在使用它)很长一段时间,这意味着我仍然需要一个解决方法。不过,我认为这是一个很好的答案! – zaphod 2008-11-05 21:09:56

1

我已经采取考文垂先生的建议和submitted a patch。然而,鉴于Python发布日程表的当前状态,即将发布3.0版本,我不认为它很快就会出现在发布版本中。不过,我们会看到会发生什么!

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

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

我隐约想象试图做一些棘手和复杂的,也许并不是那么面向未来以某种方式进口gzip模块和猴子补丁的专用副本,而是由点过滤器似乎更简单,更直接。

0

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

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