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?

War es hilfreich?

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:

  1. 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.
  2. 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.
  3. Kopieren Sie das gesamte GZIP -Modul und nennen Sie es my_stable_gzip und ändern Sie die benötigte Zeile.
  4. Geben Sie ein Cstringio -Objekt in als FileObj weiter und ändern Sie den Bytestream nach Abschluss von GZIP.
  5. 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
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top