Pythonでgzipファイルを圧縮・解凍するにはgzipモジュールを使います。

gzipファイルを圧縮・解凍するのは、Pythonで通常のファイルを読み書きする方法とほとんど同じです。通常のファイル入出力が理解できていればgzipモジュールも簡単に使えます。

Pythonの通常のファイル入出力については「Python3入門 - ファイル入出力」に詳しく書いていますので、必要であれば参照してください。

まずは基本的な概要を説明し、そのあとはサンプルコードを使って実際に動作を見ていきましょう。

gzipファイルを圧縮・解凍する手順

gzipファイルを圧縮や解凍する手順はPythonのファイル入出力の手順と同じです。

  1. with文でgzipファイルをオープンする。これにはgzip.open()関数を使う
  2. gzip.open()関数が返すファイルオブジェクトでgzipファイルを読み書きする

ファイルを開く時にはwith文を使いましょう。そうすればファイルを自動的にクローズしてくれます。

with文の使用を推奨するのはgzipモジュールのドキュメントに次のような記載があるためです。

圧縮したデータの後ろにさらに何か追加したい場合もあるので、GzipFile オブジェクトの close() メソッド呼び出しは fileobj を閉じません。 このため、書き込みのためにオープンした io.BytesIO オブジェクトを fileobj として渡し、(GzipFile を close() した後に) io.BytesIO オブジェクトの getvalue() メソッドを使って書き込んだデータの入っているメモリバッファを取得することができます。

このようにgzip.open()関数が返すファイルオブジェクトのclose()関数を呼び出しても、下層のファイルオブジェクトが閉じられないかもしれません。

gzipファイルをオープンする

gzipファイルをオープンするにはgzip.open()関数を使います。この関数の完全な構文は次の通りです。

gzip.open(filename, mode='rb', compresslevel=9, encoding=None,
          errors=None, newline=None)

引数modeのデフォルト値は、組み込みのopen()関数と異なっていますので注意してください。gzip.open()関数はデフォルトでバイナリモード(b)でファイルをオープンします。

このように組み込みのopen()関数とデフォルトのmodeが異なるりますので、バイナリモード(b)で開くか、テキストモード(t)で開くか明示的に指定した方がわかりやすいでしょう。つまり「rb」、「ab」、「xb」、「rt」、「at」、「wt」のいずれかを指定します。

引数compresslevelには0から9の整数を指定できます。デフォルトの9は最大限の圧縮(しかし最も低速)を行います。

そのほかの引数は組み込みのopen()関数と同じですので、open()関数のドキュメントを参照してください。

gzipファイルを読み書きする

gzip.open()関数が返すファイルオブジェクトは、組み込みのopen()関数が返すファイルオブジェクトと同様の読み込み関数と書き込み関数をサポートしています。

つまり書き込みにはwrite()関数、読み込みにはread()関数、readline()関数、readlines()関数などが使用できます。

ファイルオブジェクトをfor文で使うこともできます。

データからgzip圧縮ファイルを作成する

ますは、Pythonのデータからgzip圧縮ファイルを作成する方法から見ていきます。

ここでの例でテキストデータとして次の文字列を使います。

>>> str = '''海が光る
... 半分から
... こっちが光る
... '''

バイナリデータは、この文字列をutf-8でエンコードして使います。

>>> bstr = str.encode(encoding='utf-8')
>>> type(bstr)
<class 'bytes'>
>>> bstr
b'\xe6\xb5\xb7\xe3\x81\x8c\xe5\x85\x89\xe3\x82\x8b\n\xe5\x8d
\x8a\xe5\x88\x86\xe3\x81\x8b\xe3\x82\x89\n\xe3\x81\x93\xe3
\x81\xa3\xe3\x81\xa1\xe3\x81\x8c\xe5\x85\x89\xe3\x82\x8b\n'

変換したバイナリデータはbytesオブジェクトです。

また、この後のサンプルコードではgzipモジュールとshutilモジュールを使いますので次のようにインポートしておいください。

import gzip
import shutil

バイナリデータをgzip圧縮ファイルに書き出す

バイナリデータ(bytesオブジェクト)からgzip圧縮ファイルを作成するには、gzip.open()関数に「'wb'」を指定してファイルをオープンします。

gzipファイルへの書き込みにはwrite()関数を使います。

>>> with gzip.open('binfile.gz', 'wb') as fout:
...   print(type(fout))
...   fout.write(bstr)
...
<class 'gzip.GzipFile'>
45

write()関数は書き込んだバイト数を返します。ここではgzip.open()が実際に返すファイルオブジェクトを確認するため、print()関数で表示させています。

テキストデータをgzip圧縮ファイルに書き出す

テキストデータ(文字列)からgzip圧縮ファイルを作成するには、gzip.open()関数に「'wb'」を指定してファイルをオープンします。

>>> with gzip.open('txtfile.gz', 'wt') as fout:
...   print(type(fout))
...   fout.write(str)
...
<class '_io.TextIOWrapper'>
17

テキストモードでオープンすると、GzipFileオブジェクトをラップしたio.TextIOWrapperオブジェクトが返されます。

これは文字列をTextIOWrapperオブジェクトでエンコードしてGzipFileオブジェクトへ渡す必要があるからです。

書き込みにはwrite()関数を使います。この関数は書き込んだ文字数を返します。

gzip圧縮されたテキストファイルを読む

次にgzip圧縮されたテキストファイルを文字列としてPythonプログラムに読み込んでみましょう。

それにはgzip.open()関数に「'rt'」を指定してファイルをオープンします。

>>> lines = []
>>> with gzip.open('txtfile.gz', 'rt') as fin:
...   for line in fin:
...     lines.append(line)
...
>>> lines
['海が光る\n', '半分から\n', 'こっちが光る\n']

ここではファイルオブジェクトをfor文に使って、繰り返しごとに1行ずつ読み込んでいます。

もちろんread()関数、readline()関数、readlines()関数を使って読むこともできます。

gzip圧縮ファイルを解凍する

先ほど圧縮したファイルをの拡張子(.gz)を除いたファイル名に解凍してみましょう。

ここでの例は次のことを実施します。

  1. gzip圧縮ファイルをgzip.open()関数(読み込み用バイナリモード「'rb'」)で開く
  2. 通常ファイルを組み込みopen()関数(書き込み用バイナリモード「'wb'」)で開く
  3. 読み込み用のファイルオブジェクトの内容を書き込み用へコピーしてgzip圧縮ファイルへ書き込む

それではサンプルコードを見ていきましょう。

>>> with gzip.open('binfile.gz', 'rb') as fin:
...   with open('binfile', 'wb') as fout:
...     shutil.copyfileobj(fin, fout)
...
>>> with gzip.open('txtfile.gz', 'rb') as fin:
...   with open('txtfile', 'wb') as fout:
...     shutil.copyfileobj(fin, fout)
...

どちらのgzip圧縮ファイルを展開しても、解凍されたファイルに次のような内容になります。

$ cat binfile
海が光る
半分から
こっちが光る

既存のファイルをgzipで圧縮する

既存の通常ファイルをgzipファイルへ圧縮してみましょう。

次のサンプルコードでは通常ファイルをバイナリデータとして読み込んで、そのバイナリデータをgzipで圧縮してファイルへ書き込んでいます。

>>> with open('sample.txt', 'rb') as fin:
...   with gzip.open('sample.txt.gz', 'wb') as fout:
...     shutil.copyfileobj(fin, fout)
...

テキストファイルだからといって次のようにする必要はありません。

>>> with open('sample.txt', 'rt') as fin:
...   with gzip.open('sample.txt.gz', 'wt') as fout:
...     shutil.copyfileobj(fin, fout)
...

これはファイルを読み込んでテキストへデコードし、テキストをエンコードしてgzipファイルへ書き込みます。

これでもgzipへ圧縮できますが、テキストへのデコードとテキストのエンコードが冗長です。

ただし、gzipで圧縮する前にテキストファイルの文字コード変換したいというのであれば、このようにする必要があります。

gzip.open()関数の引数encodingと引数newlineについて

これまではgzipファイルの圧縮や解凍の説明のため、gzip.open()関数の引数encodingと引数newlineは使ってきませんでした。

引数encodingは文字列のエンコードとデコードを行うときの文字コード(文字エンコーディング)を指定するための引数です。引数newlineはテキストファイルの改行コードを制御するための引数です。

実際にテキストデータを扱うプログラムを書くときは、文字エンコーディングや改行コードに注意してください。そうしなければ文字化けや期待しいない改行コードでファイルへ書き込んでしまうかもしれません。

これらの引数については以下の記事が参考になりますので、よろしかったらご参照ください。

まとめ

gzipモジュールのインターフェイスはPythonの通常のファイル入出力と同じになるように設計されています。そのため使い方はそれほど難しくありません。

組み込みのopen関数もgzip.open()関数も便利なのですが、実装の詳細を隠してしまう傾向があります。ここでのサンプルコードでもgzip.open()関数が実際に返すファイルオブジェクトの型を調べていましたが、そのような下層で行われていることにも踏み込んでいくと、もっと理解が深まると思います。