我对使用Python的gzip
模块压缩数据感兴趣。碰巧我希望压缩输出是确定性的,因为这通常是一般非常方便的属性 - 如果某些非gzip感知的进程要在输出中寻找变化,比如说,或者如果输出将以加密方式签名。
不幸的是,每次输出都不同。据我所知,唯一的原因是gzip头中的timestamp字段,Python模块总是填充当前时间。我认为你实际上不允许你有一个没有时间戳的gzip流,这太糟糕了。
在任何情况下,Python的gzip
模块的调用者似乎都没有办法提供底层数据的正确修改时间。 (实际的gzip
程序似乎尽可能使用输入文件的时间戳。)我想这是因为基本上唯一关心时间戳的是写入文件时gunzip
命令 - 现在,我,因为我想要确定性输出。有这么多要问吗?
还有其他人遇到过这个问题吗?
使用Python的任意时间戳gzip
某些数据的最不可靠的方法是什么?
答案 0 :(得分:8)
是的,你没有任何漂亮的选择。在_write_gzip_header:
中使用此行写入时间write32u(self.fileobj, long(time.time()))
由于他们没有给你一种覆盖时间的方法,你可以做以下事情之一:
_write_gzip_header
函数复制到派生类中,但在这一行中使用不同的值。以下是选项#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