質問

Python を使用してデータを圧縮することに興味があります gzip モジュール。圧縮出力を決定論的にしたい場合があります。これは、一般に、何かが持っていると非常に便利なプロパティであることが多いためです。たとえば、gzip を認識しないプロセスが出力の変更を探す場合、または出力は暗号的に署名されます。

残念ながら、出力は毎回異なります。私の知る限り、これの唯一の理由は、Python モジュールが常に現在時刻を設定する gzip ヘッダーのタイムスタンプ フィールドです。タイムスタンプのない gzip ストリームを保持することは実際には許可されていないと思いますが、これは非常に残念です。

いずれにせよ、Python の呼び出し側には方法がないようです。 gzip モジュールを使用して、基礎となるデータの正しい変更時刻を提供します。(実際の gzip プログラムは可能な場合には入力ファイルのタイムスタンプを使用するようです。)これは、基本的にタイムスタンプを気にするのは gunzip ファイルに書き込むときにコマンドを実行します。そして、決定的な出力が必要なため、今は私も使用しています。そんなに聞くほどのことですか?

他にこの問題に遭遇した人はいますか?

最も怖くない方法は何ですか gzip Python からの任意のタイムスタンプを持つデータ?

役に立ちましたか?

解決

Python 2.7以降では、gzipヘッダーで使用する時間を指定できます。 N.B.ファイル名もヘッダーに含まれ、手動で指定することもできます。

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

他のヒント

ええ、あなたにはきれいなオプションはありません。時間は_write_gzip_headerの次の行で記述されます。

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

時間をオーバーライドする方法を提供しないため、次のいずれかを実行できます。

  1. GzipFileからクラスを派生し、 _write_gzip_header 関数を派生クラスにコピーしますが、この1行に異なる値を指定します。
  2. gzipモジュールをインポートした後、そのタイムメンバーに新しいコードを割り当てます。基本的にgzipコードで時間という名前の新しい定義を提供するので、time.time()の意味を変更できます。
  3. gzipモジュール全体をコピーし、my_stable_gzipという名前を付けて、必要な行を変更します。
  4. fileobjとしてCStringIOオブジェクトを渡し、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)

タイムスタンプの計算が考慮されるパッチを送信します。ほぼ間違いなく受け入れられます。

氏を連れて行きました。コベントリーのアドバイスと パッチを提出しました. 。ただし、Python のリリース スケジュールの現在の状況を考えると、3.0 が目前に迫っているため、すぐにリリースに登場するとは思えません。それでも、何が起こるか見てみましょう!

とりあえず、私は先生が好きです。Batchelder のオプション 5 は、タイムスタンプ フィールドを正しく設定する小さなカスタム フィルターを介して gzip ストリームをパイプ処理します。それが最もクリーンなアプローチのように思えます。彼が実証しているように、必要なコードは実際には非常に小さいですが、彼の例の単純さの一部は、次のような (現在有効な) 仮定に依存しています。 gzip モジュール実装は、正確に 1 回の 4 バイト呼び出しを使用してタイムスタンプを書き込むことを選択します。 write(). 。それでも、必要に応じて完全に一般的なバージョンを考案するのはそれほど難しいことではないと思います。

モンキーパッチアプローチ (別名:オプション 2) はその単純さから非常に魅力的ですが、次の呼び出しを行うライブラリを作成しているため、一時停止してしまいます。 gzip, 、単なるスタンドアロンプ​​ログラムではなく、誰かが呼び出そうとしているように思えます gzip モジュールがその変更を元に戻す準備ができる前に、別のスレッドから gzip モジュールのグローバル状態。他のスレッドが同様のモンキーパッチのスタントを実行しようとしていた場合、これは特に残念なことでしょう。この潜在的な問題が実際に発生する可能性はあまり高くないとは思いますが、このような混乱を診断することがどれほど苦痛であるか想像してみてください。

漠然と、 gzip モジュールとモンキーパッチ それ, 、しかしその時点では、フィルターはよりシンプルで直接的であるように見えます。

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()にデフォルト設定しているときに使用するオプションの時間パラメータを受け入れる必要がある場合を除き、これを行うより良い方法はありません、その場合、私は確信していますパッチを提出してくれれば、彼らはそれを気に入ってくれるでしょう!

見栄えはよくありませんが、次のように一時的に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

見栄えはよくありませんが、おそらく動作するでしょう。

上記のドミニクの回答に似ていますが、既存のファイルの場合:

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
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top