[Python入門]pickleモジュールによるオブジェクトの直列化:Python入門(1/2 ページ)
Pythonの標準ライブラリ「pickle」を使って、Pythonのオブジェクトをファイルへと保存したり、ファイルからオブジェクトを復元したりする方法を紹介する。
前回は、バイナリファイルの操作の基本と、structモジュールを使ってのオブジェクトのバイナリファイルへの読み書きについて見た。今回は、Pythonのオブジェクトをファイルに保存するために使えるpickleモジュールを紹介する。
今回の目次
オブジェクトの直列化
本連載で使っているJupyter Notebookのような対話的環境では、ある程度の範囲のコードを実行しては、ダメなら前に戻ってコードを修正してもう一度実行ということがよくある。また、「今日はここまで」として環境を閉じて、翌日に続きから作業を行うこともあるだろう。
例えば、pandasと呼ばれるデータ解析ライブラリでは、巨大なサイズのCSVファイルを読み込んで、それにさまざまな形で手を加えることがよくある。そうしたときには、CSVファイルを読み込んで、パースするだけでも時間がかかることがよくある。そうした場合には、CSVファイルを読み込んで、いろいろな操作を加えた時点で、それらを一度ファイルに保存しておくと、その後、それを読み込むだけで以前の作業結果を復元できる(以下の画像ではサンプルデータとして、多くのデータサイエンティストや機械学習エンジニアが集まるコミュニティーであるKaggleでCC0、パブリックドメインとして公開されているデータ「International football results from 1872 to 2019」を使用している)。
このようなときには、それまでに作業した成果をファイルに保存しておいて、後で(または後日に)ファイルに保存しておいたものを取り出せると、以前の作業を無駄にすることなく、仕事を続けられる。
あるいは、Pythonプログラムの実行中にプログラムの終了が命令されたときに、プログラムの動作に関わるデータや、ユーザーデータなどをファイルに保存すれば、プログラムの再起動時にそのデータを読み込むことで、以前の処理を続けられるようになる。
このとき、ファイルとは「先頭から末尾までを順番にアクセスする」ものであることに注意しよう(現在、ファイルのどの位置をアクセスしているかは「ファイル位置」で示されることは第38回「ファイル操作の基本」で述べた)。つまり、ファイルとはPythonのシーケンスと同様、1つ1つのデータが連なった列のようなものだと考えられる。
つまり、ファイルにデータを保存するには、Pythonのオブジェクトもそれに合わせて「列」状のデータにする必要がある。オブジェクトをそのように変換することを「直列化」と呼ぶ。直列化したデータはファイルに保存する以外にも、別のマシンに送信するといった利用法もある。
本稿では、Pythonでオブジェクトを直列化して、ファイルに保存したり、あるいは直列化されたデータから元のオブジェクトを復元したりするために使えるpickleモジュールを紹介しよう。
pickleモジュール
pickleモジュールは、Pythonの各種オブジェクトを前回に見たバイト列(bytesオブジェクト)に変換したり、変換したバイト列をファイルに保存したりするために使える。あるいは、その逆にメモリ上のバイト列やファイルに保存されたバイト列からPythonオブジェクトを復元するためにも使える。
pickleモジュールでは、Pythonのオブジェクトを直列化する(バイト列に変換してファイルに保存する)ことを「pickle化」「ピクル化」と呼び、直列化されたバイト列からPythonオブジェクトに復元することを「非pickle化」「非ピクル化」と呼ぶ。
pickleモジュールを使うと、多くのPythonオブジェクトをpickle化、非pickle化できるが、あらゆるPythonオブジェクトでそれが可能なわけではないことには注意しよう。
ファイルやバイト列へのpickle化
ファイルへのpickle化にはpickleモジュールのdump関数を、バイト列(bytesオブジェクト)へのpickle化にはdumps関数を使用する。以下にこれらの基本構文を示す。
dump/dumps関数
dump(obj, file, protocol=None)
dumps(obj, protocol=None)
dump関数はパラメーターobjに与えられたオブジェクトをbytesオブジェクトにpickle化したものを、パラメーターfileに与えられたファイル(オブジェクト)に書き込む。dumps関数はパラメーターobjに与えられたオブジェクトをbytesオブジェクトにpickle化したものを戻り値とする。両者の関数では、パラメーターprotocolに与えられたプロトコルでオブジェクトがbytesオブジェクトにpickle化される。
パラメーター | 説明 |
---|---|
obj | pickle化の対象となるPythonオブジェクト |
file | pickle化されたオブジェクトを書き込む先のファイル |
protocol | pickle化に使用するプロトコルのバージョン |
dump/dumps関数のパラメーター |
パラメーターobjにはpickle化したいオブジェクトを与える。dump関数のパラメーターfileには、前回に紹介した書き込み用にオープンしたバイナリファイルを与える。最後のパラメーターprotocolには0〜4のプロトコルバージョンを与える。省略した場合、デフォルトのプロトコルが使用される*1。
*1 Python 3.8では、pickleのプロトコルバージョン5が導入された。これはPythonが機械学習の分野で広く使われるようになり、サイズの大きなデータをバイト列に変換して、マルチコアCPUや複数台のPC上の複数プロセスを使って、それらを処理できるように対応するためだ。また、メジャーバージョンが3のPythonでは長らくデフォルトのプロトコルは3だったのが、Python 3.8ではデフォルトのプロトコルが4になった。
プロトコルのバージョンを以下に示す。
- 0:人が読める形式の(ASCII文字のみを使った)プロトコル
- 1:古い形式のバイナリフォーマット。バージョン0と1は古いPythonとの互換性を持つ
- 2:Python 2.3で導入されたプロトコル。バージョン2のプロトコルを使うと、Python 3で全面的に使われている(新方式の)クラスのより効率的なpickle化が可能(ただし、以前のプロトコルとの互換性がない)
- 3:Python 3.0〜Python 3.7におけるデフォルトのプロトコル(Python 3.0で導入)。Python 2.xとの互換性がない
- 4:Python 3.4で導入された。巨大なサイズのデータのpickle化、pickle化可能なオブジェクトの増加などの改善点がある
- 5:Python 3.8で導入されたプロトコル。大サイズのデータを、メモリコピーを不必要に発生させることなく効率的にpickle化して、複数プロセス間でやりとりできるようになった
以下に使用例を示す。ここではプロトコルのバージョンを指定していないので、デフォルトのプロトコル(この環境では「3」)が使われる。
import pickle
mydata = [{'name': 'shinji kawasaki', 'age': 120, 'height': 230, 'weight': 300},
{'name': 'isshiki masahiko', 'age': 60, 'height': 180, 'weight': 60}]
some_data = 100
another_data = 'deep insider'
myfile = open('mydata.pickle', 'wb')
pickle.dump(mydata, myfile)
pickle.dump(some_data, myfile)
pickle.dump(another_data, myfile)
myfile.close()
これは、dump関数を使って、辞書を要素とするリストとその他のデータを順番に「mydata.pickle」ファイルへと書き出しているところだ。このようにプログラムの動作に必要と思われるデータは順番にdump関数を使って保存できる(その後、読み出すときには書き込んだ順番にそれらが取り出される。これについては後で見る)。
pickle化により(Python 3では)オブジェクトはbytesオブジェクトに変換され、それらがファイルに書き込まれるので、書き込み先のファイルのオープンではパラメーターmodeに「'wb'」を指定しているのを忘れないようにしよう。
実際には、どのようなbytesオブジェクトに変換されるのを、今度はdumps関数を使って確認してみよう。
pickled_mydata = pickle.dumps(mydata)
pickled_some_data = pickle.dumps(some_data)
pickled_another_data = pickle.dumps(another_data)
print('mydata:', pickled_mydata)
print('some_data:', pickled_some_data)
print('another_data:', pickled_another_data)
実行結果を以下に示す。
3つのバイト列が出力されているが、その中でも一番簡単そうな変数some_dataの値「100」をpickle化した結果である「b'\x80\x03Kd.'」について説明しておこう。最初の「\x80\x03」はこのデータがpickleのプロトコルバージョン3でpickle化されていることを表している。次の「K」はその直後にある「d」が「1バイトの符号なし整数」であることを意味する。そして「ord('d')」は「100」なので、これがbytesオブジェクトにpickle化される対象のデータそのものの値だ。最後の「.」はpickle化が完了したことを表す記号となる。
詳しいことは知らなくても構わないが、何となくこんな感じでpickle化が行われていることは知っておこう。興味のある方はpickle.pyのソースコードなどを参照してほしい。
ファイルやバイト列からの非pickle化
次にpickle化されたオブジェクトを非pickle化して、Pythonオブジェクトに復元してみよう。
ファイルに格納されているpickle化されたbytesオブジェクトを復元するにはpickleモジュールのload関数を、メモリ上にpickle化されたbytesオブジェクトを復元するにはloads関数を使用する。これらの基本構文を以下に示す。
load/loads関数
load(file)
loads(bytes_obj)
load関数はパラメーターfileに指定されたファイルオブジェクトに格納されているpickle化されたオブジェクトを1つずつ取り出す。loads関数はパラメーターbytes_objからオブジェクトを復元する
パラメーター | 説明 |
---|---|
file | pickle化されたオブジェクトが格納されているファイル |
bytes_obj | pickle化されたオブジェクトを表すbytesオブジェクト |
load/loads関数のパラメーター |
先ほどの整数値「100」をpickle化した結果の説明で述べたように、pickle化されたデータにはどのプロトコルが使われているかが含められている。そのため、load/loads関数ではプロコトルバージョンの指定は不要だ。
では、上でpickle化したデータをこれらの関数を使って復元してみよう。
myfile = open('mydata.pickle', 'rb')
rmydata = pickle.load(myfile)
rsome_data = pickle.load(myfile)
ranother_data = pickle.load(myfile)
print(rmydata)
print(rsome_data)
print(ranother_data)
ファイルから非pickle化するときには、読み込むファイルはバイナリモードでオープンする必要があることには注意しよう。あとは、ファイルオブジェクトをpickleモジュールのload関数に渡せば、そこから順番にpickle化されたデータを取り出せる(ここでは、取り出したデータを「restored」を意味する「r」を前置した変数に代入している)。
実行結果を以下に示す。復元できていることを確認しよう。
bytesオブジェクトから復元するときには、loads関数にpickle化により得られたbytesオブジェクトを渡すだけだ。
rmydata = pickle.loads(pickled_mydata)
rsome_data = pickle.loads(pickled_some_data)
ranother_data = pickle.loads(pickled_another_data)
print(rmydata)
print(rsome_data)
print(ranother_data)
結果は上と同様なので、ここでは省略する。
Copyright© Digital Advantage Corp. All Rights Reserved.