Setzen Sie den Gzip -Zeitstempel aus Python
Frage
Ich bin daran interessiert, Daten mithilfe von Python zu komprimieren gzip
Modul. Es kommt vor, dass ich möchte, dass die komprimierte Ausgabe deterministisch ist, da dies oft eine wirklich bequeme Eigenschaft für Dinge ist, die im Allgemeinen haben zu haben-wenn ein nicht gziperer Prozess nach Änderungen der Ausgabe sucht oder wenn Die Ausgabe wird kryptografisch signiert.
Leider ist die Ausgabe jedes Mal anders. Soweit ich das beurteilen kann, ist der einzige Grund dafür das Zeitstempelfeld im GZIP -Header, das das Python -Modul immer mit der aktuellen Zeit bevölkert. Ich glaube nicht, dass Sie tatsächlich einen GZIP -Stream ohne Zeitstempel haben dürfen, was schade ist.
Auf jeden Fall scheint es keinen Weg für den Anrufer von Python zu geben gzip
Modul zur korrekten Änderungszeit der zugrunde liegenden Daten. (Das tatsächliche gzip
Das Programm scheint nach Möglichkeit den Zeitstempel der Eingabedatei zu verwenden.) Ich stelle mir vor, dass dies das einzige, was sich jemals um den Zeitstempel kümmert gunzip
Befehl beim Schreiben in eine Datei - und jetzt mir, weil ich eine deterministische Ausgabe möchte. Ist das so viel zu fragen?
Hat noch jemand auf dieses Problem gestoßen?
Was ist der am wenigsten schreckliche Weg zu gzip
Einige Daten mit einem willkürlichen Zeitstempel von Python?
Lösung
Ab Python 2.7 können Sie die Zeit angeben, die im GZIP -Header verwendet werden soll. Der NB -Dateiname ist ebenfalls im Header enthalten und kann auch manuell angegeben werden.
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()
Andere Tipps
Ja, Sie haben keine hübschen Optionen. Die Zeit wird mit dieser Zeile in _write_gzip_header geschrieben:
write32u(self.fileobj, long(time.time()))
Da sie Ihnen keine Möglichkeit geben, die Zeit außer Kraft zu setzen, können Sie eines dieser Dinge tun:
- Leiten Sie eine Klasse von GZIPFILE ab und kopieren Sie die
_write_gzip_header
Funktionieren Sie in Ihre abgeleitete Klasse, jedoch mit einem anderen Wert in dieser einen Zeile. - Nach dem Importieren des GZIP -Moduls dem Zeitmitglied neuen Code zuweisen. Sie werden im Wesentlichen eine neue Definition der Namenszeit im GZIP -Code bereitstellen, sodass Sie wann Zeitzeit () ändern können.
- Kopieren Sie das gesamte GZIP -Modul und nennen Sie es my_stable_gzip und ändern Sie die benötigte Zeile.
- Geben Sie ein Cstringio -Objekt in als FileObj weiter und ändern Sie den Bytestream nach Abschluss von GZIP.
- Schreiben Sie ein gefälschtes Dateiobjekt, das die geschriebenen Bytes verfolgt und alles an eine echte Datei übergibt, mit Ausnahme der Bytes für den Zeitstempel, den Sie selbst schreiben.
Hier ist ein Beispiel für Option 2 (ungetestete):
class FakeTime:
def time(self):
return 1225856967.109
import gzip
gzip.time = FakeTime()
# Now call gzip, it will think time doesn't change!
Option 5 kann das sauberste sein, was nicht von den Interna des Gzip -Moduls (nicht getestet) abhängig ist:
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)
Sende ein Patch in der die Berechnung der Zeitstempel berücksichtigt wird. Es würde mit ziemlicher Sicherheit akzeptiert werden.
Ich habe Mr. Coventrys Rat beanstandet und Ein Patch eingereicht. Angesichts des aktuellen Standes des Python -Veröffentlichungsplans mit 3.0 gleich um die Ecke erwarte ich jedoch nicht, dass er bald in einer Veröffentlichung angezeigt wird. Trotzdem werden wir sehen, was passiert!
In der Zwischenzeit mag ich die Option 5 von Mr. Batchelder, den GZIP -Stream durch einen kleinen benutzerdefinierten Filter zu leiten, der das Feld des Zeitstempels korrekt festlegt. Es klingt nach dem saubersten Ansatz. Wie er zeigt, ist der erforderliche Code tatsächlich recht klein, obwohl sein Beispiel für einen Teil seiner Einfachheit von der (derzeit gültigen) Annahme hängt, dass die gzip
Die Modulimplementierung entscheidet sich für das Schreiben des Zeitstempels mit genau einem Vier-Byte-Aufruf an write()
. Trotzdem denke ich nicht, dass es sehr schwierig wäre, bei Bedarf eine vollständig allgemeine Version zu finden.
Der Ansatz von Affenpatching (auch bekannt als Option 2) ist für seine Einfachheit sehr verlockend, gibt mir aber eine Pause, weil ich eine Bibliothek schreibe, die anruft gzip
, nicht nur ein eigenständiges Programm, und es scheint mir, dass jemand versucht, anzurufen gzip
Von einem anderen Thread vor meinem Modul bereit ist bereit, seine Änderung in die umzukehren gzip
Der globale Zustand des Moduls. Dies wäre besonders unglücklich, wenn der andere Faden versuchen würde, einen ähnlichen Stunt zu ziehen! Ich gebe zu, dass dieses potenzielle Problem in der Praxis nicht sehr wahrscheinlich klingt, aber stellen Sie sich vor, wie schmerzhaft es wäre, ein solches Durcheinander zu diagnostizieren!
Ich kann mir vage vorstellen, etwas Tricksiges und Kompliziertes zu tun und vielleicht nicht so zukunftssicher, um eine private Kopie der zu importieren gzip
Modul und Affenpatch das, Aber zu diesem Zeitpunkt scheint ein Filter einfacher und direkter zu sein.
In lib/gzip.py finden wir die Methode, die den Header baut, einschließlich des Teils, der tatsächlich einen Zeitstempel enthält. In Python 2.5 beginnt dies in Zeile 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')
Wie Sie sehen können, nutzt es Time.time (), um die aktuelle Zeit zu holen. Laut den Online -Modul -Dokumenten wird Time.Time "die Zeit als schwimmende Punktzahl zurückgeben, die seit der Epoche in UTC in Sekunden ausgedrückt wird". Wenn Sie dies also in eine schwimmende Punktkonstante Ihrer Wahl ändern, können Sie immer die gleichen Header ausschreiben lassen. Ich kann keinen besseren Weg sehen, dies zu tun, wenn Sie der Bibliothek noch etwas mehr hacken möchten, um einen optionalen Zeitparam zu akzeptieren, den Sie während der Defaugung zur Zeit verwenden. Sie würden es lieben, wenn Sie einen Patch einreichen!
Es ist nicht hübsch, aber Sie könnten eine MonkeyPatch -Zeit haben.
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
Es ist nicht schön, aber es würde wahrscheinlich funktionieren.
Ähnlich der Antwort von Dominics oben, aber für eine vorhandenen Datei:
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)
Testen von MD5 -Summen:
md5sum test_zip*
7e544bc6827232f67ff5508c8d6c30b3 test_zip1
75decc5768bdc3c98d6e598dea85e39b test_zip1.gz
7e544bc6827232f67ff5508c8d6c30b3 test_zip2
75decc5768bdc3c98d6e598dea85e39b test_zip2.gz