Pythonのzlibとは
Python zlibライブラリは、DEFLATE可逆圧縮アルゴリズムの高位抽象化である zlib CライブラリへのPythonインターフェイスを提供します。このライブラリで使われるデータフォーマットはRFC1950から1952で指定されており、http://www.ietf.org/rfc/rfc1950.txt。
zlib圧縮形式は無料で使用でき、いかなる特許もカバーしていないので、商用製品にも安全に使用することができます。可逆圧縮形式 (圧縮と解凍の間でデータを失わない) であり、 異なるプラットフォーム間で移植可能という利点があります。この圧縮機構のもう一つの重要な利点は、データを拡張しないことです。
zlibライブラリの主な用途は、文字列、構造化されたインメモリコンテンツ、ファイルなど、任意のデータの圧縮・伸張を必要とするアプリケーションです。
このライブラリに含まれる最も重要な機能は、圧縮と解凍です。圧縮と伸張は、どちらも一回きりの操作で行うことができ、また、データのストリームから見えるように、データをチャンクに分割して行うこともできる。どちらの操作モードも、この記事で説明しています。
私が思うに、zlibライブラリの最も優れた点の1つは、Unixシステムで最も広く使われている圧縮アプリケーションの1つであるgzipファイルフォーマット/ツール(これもDEFLATEをベースにしています)と互換性があることです。
圧縮率
文字列の圧縮
zlib ライブラリは compress
という関数を提供しており、これを使うとデータの文字列を圧縮することができます。この関数の構文は非常に単純で、2つの引数を取るだけです。
compress(data, level=-1)
ここで、 data
には圧縮するバイトを指定し、 level
には -1 または 0 から 9 の値を取る整数値を指定します。このパラメータは圧縮のレベルを決定し、レベル 1 は最も速く、最も低いレベルの圧縮を生成します。レベル9は最も低速ですが、圧縮率は最も高くなります。値-1は、デフォルトのレベル6を表します。デフォルト値は、速度と圧縮のバランスが取れています。レベル0は圧縮されない。
以下に、単純な文字列に対して compress
メソッドを使用した例を示します。
import zlib
import binascii
data = 'Hello world'
compressed_data = zlib.compress(data, 2)
print('Original data: ' + data)
print('Compressed data: ' + binascii.hexlify(compressed_data))
そして、結果は以下のようになります。
$ python compress_str.py
Original data: Hello world
Compressed data: 785ef348cdc9c95728cf2fca49010018ab043d
図1
レベルを0(圧縮しない)に変更すると、5行目は次のようになる。
compressed_data = zlib.compress(data, 0)
そして、新しい結果は次のようになります。
$ python compress_str.py
Original data: Hello world
Compressed data: 7801010b00f4ff48656c6c6f20776f726c6418ab043d
図2
圧縮レベルとして 0
と 2
を使用した場合の出力を比較すると、いくつかの違いがあることに気がつくかもしれません。2を使用すると、長さ 38 の文字列 (16進数でフォーマット) が得られますが、
0を使用すると、長さ 44 の 16 進文字列が得られます。この長さの違いは、レベル
0` を使用した場合に圧縮が行われないことに起因します。
この例で行ったように、文字列を16進数でフォーマットしないで出力データを見ると、入力文字列は「圧縮」された後でも、周囲にいくつかの余分なフォーマット文字があるものの、まだ読めることに気がつくと思われます。
大容量データストリームの圧縮
大きなデータストリームは、圧縮オブジェクトを返す compressobj()
関数で管理することができる。その構文は以下の通りである。
compressobj(level=-1, method=DEFLATED, wbits=15, memLevel=8, strategy=Z_DEFAULT_STRATEGY[, zdict])
この関数の引数と compress()
関数の主な違いは、(data
パラメータは別として) wbits
引数であり、ウィンドウサイズと、ヘッダーとトレイラーが出力に含まれるかどうかを制御する。
wbits` に指定できる値は次のとおりです。
| Value|ウィンドウサイズの対数|Output||。
| — | — | — |
| +9 to +15 | ベース2 | zlibヘッダーとトレイラーを含む | -9 to -15 | 絶対値
| -9 から -15 | wbits の絶対値 | ヘッダーとトレイラーなし | -25 から +31 | 最低値
| 25から+31|値の下位4ビット|gzipヘッダと末尾のチェックサムを含む|。
表1
引数 method
は使用する圧縮アルゴリズムを表す。現在利用できる値は DEFLATED
のみであり、これは RFC 1950 で定義されている唯一の方法である。strategy` 引数は圧縮のチューニングに関連する。本当に何をしているのかわからない限り、この引数は使用せず、デフォルト値を使用することを推奨する。
次のコードは compressobj()
関数の使い方を示している。
import zlib
import binascii
data = 'Hello world'
compress = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15)
compressed_data = compress.compress(data)
compressed_data += compress.flush()
print('Original: ' + data)
print('Compressed data: ' + binascii.hexlify(compressed_data))
このコードを実行すると、結果は次のようになります。
$ python compress_obj.py
Original: Hello world
Compressed data: f348cdc9c95728cf2fca490100
図3
上の図からわかるように、「Hello world」というフレーズが圧縮されていることがわかる。一般的にこの方法は、一度にメモリに収まらないようなデータストリームを圧縮するために使われます。この例では、それほど大きなデータストリームはありませんが、compressobj()
関数の仕組みを示す目的には十分です。
また、より大きなアプリケーションで圧縮の設定を行い、その圧縮オブジェクトを他のメソッドやモジュールに渡すと便利であることがわかるかもしれません。これは、一連のデータの塊を圧縮するために使用することができます。
また、圧縮するデータストリームがある場合にも有用であることがわかるだろう。すべてのデータをメモリに蓄積する代わりに、データチャンクに対して compress.compress(data)
と compress.flush()
を呼び出すだけで、前のチャンクはガベージコレクションでクリーンアップしたまま、次のチャンクに移ることができるのです。
ファイルを圧縮する
compress()` 関数を使用して、ファイルのデータを圧縮することもできます。構文は、最初の例と同じです。
以下の例では、”logo.png” という名前の PNG イメージファイルを圧縮します (注意: これはすでにオリジナルの生画像を圧縮したものです)。
コード例は次のとおりです。
import zlib
original_data = open('logo.png', 'rb').read()
compressed_data = zlib.compress(original_data, zlib.Z_BEST_COMPRESSION)
compress_ratio = (float(len(original_data)) - float(len(compressed_data))) / float(len(original_data))
print('Compressed: %d%%' % (100.0 * compress_ratio))
上記のコードでは、zlib.compress(...)
行で定数 Z_BEST_COMPRESSION
を使用しています。これは、その名前が示すように、このアルゴリズムが提供する最高の圧縮レベルを提供します。次の行では、圧縮後のデータの長さと元のデータの長さの比に基づいて、圧縮のレベルを計算します。
その結果は次のようになる。
$ python compress_file.py
Compressed: 13%
図4
見てわかるように、このファイルは13%圧縮されています。
この例と最初の例との唯一の違いは、データのソースです。しかし、単なるASCII文字列やバイナリ画像データであっても、どのようなデータを圧縮できるかを知ってもらうために、示すことが重要だと考えています。通常のようにファイルからデータを読み込んで、compress
メソッドを呼び出すだけです。
圧縮したデータをファイルに保存する
圧縮されたデータは、ファイルに保存して後で使用することもできます。以下の例では、圧縮されたテキストをファイルに保存しています。
import zlib
my_data = 'Hello world'
compressed_data = zlib.compress(my_data, 2)
f = open('outfile.txt', 'w')
f.write(compressed_data)
f.close()
上記の例では、”Hello world “という文字列を圧縮し、”outfile.txt “という名前のファイルに保存しています。このoutfile.txtをテキストエディタで開くと、以下のようになります。
図5
減圧
文字列のデータ圧縮を解除する
圧縮されたデータの文字列は、decompress()
関数を用いて簡単に伸長することができる。その構文は以下の通りである。
decompress(data, wbits=MAX_WBITS, bufsize=DEF_BUF_SIZE)
この関数は、引数 data
に含まれるバイト列を伸長する。引数 wbits
は、ヒストリバッファのサイズを管理するために使用することができる。デフォルト値は、最大のウィンドウサイズに一致する。また、圧縮ファイルのヘッダとトレーラを含めるかどうかを尋ねる。可能な値は次のとおりです。
| Value | Window size logarithm | Input | (入力
| — | — | — |
| 8から15|ベース2|zlibヘッダーとトレーラを含む|-8から-15|絶対値
| 8から-15 | wbitsの絶対値 | ヘッダーとトレーラーのない生のストリーム | 24から-31 = 16。
| 24から+31 = 16 + (8から15) | 値の下位4ビット| gzipヘッダーとトレイラーを含む| -10から-15| wbitsの絶対値。
| 40から47 = 32 + (8から15) | 値の下位4ビット | zlibまたはgzipフォーマット|。
表2
バッファサイズの初期値は bufsize
引数で示されます。しかし、このパラメータについて重要な点は、バッファサイズを増やす必要がある場合は自動的に増やされるため、正確に指定する必要がないことである。
次の例は、先ほどの例で圧縮された文字列を伸長するものである。
import zlib
data = 'Hello world'
compressed_data = zlib.compress(data, 2)
decompressed_data = zlib.decompress(compressed_data)
print('Decompressed data: ' + decompressed_data)
結果は次のようになります。
$ python decompress_str.py
Decompressed data: Hello world
図5
大規模データストリームの伸長
大きなデータストリームを伸長する場合、データのサイズやソースによってメモリ管理が必要になることがあります。このタスクに利用可能なメモリをすべて使用できない (あるいはメモリが足りない) 可能性があるので、decompressobj()
メソッドを使用すると、データのストリームをいくつかのチャンクに分割して、別々に解凍することができます。
decompressobj()` 関数のシンタックスは以下のとおりである。
decompressobj(wbits=15[, zdict])
この関数は伸長オブジェクトを返し、それを使って個々のデータを伸長する。引数 wbits
は、先に説明した decompress()
関数と同じ性質を持っています。
次のコードは、ファイルに格納された大きなデータストリームを解凍する方法を示しています。まず、圧縮されたデータを格納した “outfile.txt” という名前のファイルを作成します。データは wbits
の値が +15 になるように圧縮されていることに注意すること。これにより、データ中にヘッダとトレーラが作成される。
次に、ファイルはデータのチャンクを使って伸長される。この例でも、ファイルには大量のデータは含まれていませんが、それでもバッファの概念を説明する目的には十分です。
コードは次のとおりです。
import zlib
data = 'Hello world'
compress = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, +15)
compressed_data = compress.compress(data)
compressed_data += compress.flush()
print('Original: ' + data)
print('Compressed data: ' + compressed_data)
f = open('compressed.dat', 'w')
f.write(compressed_data)
f.close()
CHUNKSIZE = 1024
data2 = zlib.decompressobj()
my_file = open('compressed.dat', 'rb')
buf = my_file.read(CHUNKSIZE)
# Decompress stream chunks
while buf:
decompressed_data = data2.decompress(buf)
buf = my_file.read(CHUNKSIZE)
decompressed_data += data2.flush()
print('Decompressed data: ' + decompressed_data)
my_file.close()
上記のコードを実行すると、次のような結果が得られます。
$ python decompress_data.py
Original: Hello world
Compressed data: x??H???W(?/?I?=
Decompressed data: Hello world
図6
ファイルからデータを解凍する
これまでの例で見てきたように、ファイルに含まれる圧縮されたデータは簡単に解凍することができます。この例は、ファイルからデータを解凍するという点で前回の例と非常に似ていますが、今回は 1 回のメソッドコールでデータを解凍する decompress
メソッドを使用します。これは、データがメモリに簡単に収まるほど小さい場合に便利です。
これは、次の例からわかる。
import zlib
compressed_data = open('compressed.dat', 'rb').read()
decompressed_data = zlib.decompress(compressed_data)
print(decompressed_data)
上のプログラムは、前の例で作成した圧縮ファイル “compressed.dat” を開いて、圧縮された “Hello world” という文字列を格納している。
この例では、圧縮されたデータを取り出して変数 compressed_data
に格納した後、ストリームを伸長してその結果を画面に表示している。ファイルに含まれるデータ量が少ないので、この例では decompress()
関数を使用しています。しかし、前の例で示したように、 decompressobj()
関数を使用してデータを解凍することもできます。
プログラムを実行すると、次のような結果が得られます。
$ python decompress_file.py
Hello world
図7
まとめ
Pythonのライブラリであるzlibは、zlibフォーマットを使ったファイル圧縮のための便利な関数群を提供してくれています。通常は、関数 compress()
と decompress()
が使用されます。しかし、メモリに制約がある場合には、データストリームの圧縮/伸長をサポートする compressobj()
および decompressobj()
という関数が利用でき、より柔軟な対応が可能になっています。これらの関数は、データをより小さく扱いやすい塊に分割するのに役立ちます。これらの塊は、それぞれ compress()
と decompress()
という関数を使って圧縮または伸張することができます。
zlib ライブラリには、この記事で紹介した以外にも多くの機能があることを覚えておいてください。例えば、zlib を使ってあるデータのチェックサムを計算し、解凍したときにその整合性を検証することができます。このような追加機能の詳細については、公式ドキュメントを参照してください。