9

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

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

gzipいずれにせよ、Pythonのモジュールの呼び出し元が、基になるデータの正しい変更時刻を提供する方法はないようです。(実際のgzipプログラムは、可能な場合は入力ファイルのタイムスタンプを使用しているようです。)これは、基本的にタイムスタンプを気にするのはgunzip、ファイルに書き込むときのコマンドだけだからだと思います。決定論的な出力が必要です。質問することはそんなにありますか?

他の誰かがこの問題に遭遇しましたか?

gzipPythonからの任意のタイムスタンプを持つデータへの最もひどい方法は何ですか?

4

7 に答える 7

8

ええ、きれいなオプションはありません。時刻は、_write_gzip_header に次の行で書き込まれます。

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

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

  1. GzipFile からクラスを派生させ、_write_gzip_header関数を派生クラスにコピーしますが、この 1 行の値は異なります。
  2. gzip モジュールをインポートしたら、その time メンバーに新しいコードを割り当てます。基本的に、gzip コードで time という名前の新しい定義を提供することになるため、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)
于 2008-11-05T03:49:51.973 に答える
2

タイムスタンプの計算を除外したパッチを提出してください。ほぼ間違いなく受け入れられるでしょう。

于 2008-11-05T15:17:54.923 に答える
1

コベントリー氏のアドバイスを受けて、パッチを提出しました。しかし、Python のリリース スケジュールの現在の状態を考えると、3.0 が間近に迫っているため、すぐにリリースされるとは思えません。それでも、どうなるか見てみましょう!

それまでの間、タイムスタンプ フィールドを正しく設定する小さなカスタム フィルターを介して gzip ストリームをパイプするという Batchelder 氏のオプション 5 が気に入っています。それは最もクリーンなアプローチのように聞こえます。gzip彼が示しているように、必要なコードは実際には非常に小さいものですが、彼の例は、モジュールの実装が への 4 バイト呼び出しを 1 回だけ使用してタイムスタンプを書き込むことを選択するという (現在有効な) 仮定にいくつかの単純さを依存していますwrite()。それでも、必要に応じて完全に一般的なバージョンを作成することはそれほど難しくないと思います。

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

トリッキーで複雑で、おそらくそれほど将来性のない何かをしようとしてgzipモジュールのプライベート コピーとモンキー パッチインポートしようとすることを漠然と想像できますが、その時点ではフィルターはよりシンプルで直接的なものに見えます。

于 2008-11-06T21:17:09.707 に答える
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() にデフォルト設定するときに使用するオプションの時間パラメータを受け入れるためにライブラリをもう少しハックしたい場合を除き、これを行うためのより良い方法はわかりません。あなたがパッチを提出してくれたら、彼らはそれを喜ぶでしょう!

于 2008-11-05T03:44:19.397 に答える
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

きれいではありませんが、おそらくうまくいくでしょう。

于 2008-11-05T04:32:23.747 に答える