pickleモジュールのドキュメントの「pickle 化、非 pickle 化できるもの」には、pickleモジュールを使ってpickle化が可能なオブジェクトが列挙されている。いかにも幾つかを示しておこう。
これら以外のオブジェクトは、pickle化の対象とはならず、pickle化しようとするとエラーが発生する。pickle化できない代表的なオブジェクトがファイルオブジェクトだ。そこで、これをpickle化してみよう。
myfile = open('mydata.pickle', 'rb')
pickle.dumps(myfile)
実行結果を以下に示す。
このように例外が発生した(ただし、発生する例外は場合によってさまざまだ)。
また、pickle化できる場合にも制約があることに注意しよう。例えば、関数やクラスをpickle化するというのは「その関数やクラスの名前がpickle化される」ことを意味する。それらの定義を収めたコードがpickle化されるわけではない。例として、関数を定義して、それをpickle化した後、del文で関数を削除してから、非pickle化してみよう。
def hello():
print('hello world')
pickled_hello = pickle.dumps(hello)
print(pickled_hello)
restored_hello = pickle.loads(pickled_hello)
restored_hello()
del hello
restored_hello = pickle.loads(pickled_hello)
このコードでは、変数pickled_helloにhello関数をpickle化したものを代入している。そして、hello関数が存在している間に一度、変数pickled_helloの値を非pickle化して、その戻り値を変数restored_helloに代入し、それを呼び出している。その後、hello関数を削除してから、もう一度、非pickle化している。
このコードを実行すると次のようになる。
「hello world」という出力結果があるので、restored_hello関数が呼び出されて、問題なく実行できたことが分かる。だが、その後には例外が発生したことを示すメッセージが表示されている。そのメッセージの内容は「__main__モジュールにhelloという属性がない」というものだ。これは現在の対話環境(__main__モジュール)にhello関数がないことを示している。もちろん、del文でhello関数を削除しているのでこれは正しい。つまり、pickle化と非pickle化を行っている環境の両者で、同じ関数が存在しないと、非pickle化は失敗するということだ。
この状態で、同じ名前の別の関数を定義して、非pickle化してみよう。
def hello(whom):
print('goodbye', whom)
restored_hello = pickle.loads(pickled_hello)
restored_hello('world')
実行結果を以下に示す。
今度は非pickle化は成功するが、最初のhello関数とは異なる関数がrestored_hello関数の内容となった。同様なことは(クラスのインスタンスではなく)クラスについてもいえる。このことから、pickle化と非pickle化を行う環境では、使っているクラスや関数などを一致させる必要があり、それはpickle化と非pickle化を行うプログラマーが考慮しなければならないことには注意しよう。
pickleモジュールのドキュメントの冒頭部分には以下のような警告が書かれている。
最後に、これがどういう意味なのかを簡単に説明しておこう。
まず、以下の内容のファイルに「insecure.pickle」というファイル名を付けて保存しておこう(環境によっては改行コードを変更する必要があるかもしれない)。よく見ると、その内容が想像できるかもしれないが、これについては後で見てみよう。
c__builtin__
exec
(S'def hello(to): print("hello " + to)'
tR0c__main__
hello
(S'world'
tR.
以下はJupyter Notebook環境のファイル一覧ページからファイルを新規作成して、そこに上の内容を記述して、「insecure.pickle」という名前で保存したところだ。
拡張子が「pickle」となっていることから分かるように、これは非pickle化が可能な内容のファイルを手書きで作成したものだ。というわけで、これを非pickle化してみよう。
myfile = open('insecure.pickle', 'rb')
restored_data = pickle.load(myfile)
myfile.close()
これを実行すると、次のようになる。
何やら「hello world」という文字列が表示された。非pickle化に際して、「hello world」という文字列を表示するコードが実行されたということだ。
詳しくは説明しないが、insecure.pickleファイルに書かれたpickle化されたバイト列の内容はPythonのexec関数を使ってhello関数を定義して、引数を'world'として、その関数を呼び出すというものだった。
つまり、出所不明のpickle化されたファイルを、安易に非pickle化すると、意図しないままに何らかのコードが実行される可能性がある。そのため、pickleモジュールのドキュメントには「信頼できない(中略)ソースから受け取ったデータを非pickle化してはいけません」と書いてあるということだ。
非pickle化を行う場合には、自分が作成しているデータや信頼できるソースから入手したものだけをその対象とするようにしよう。
今回はPythonでオブジェクトの直列化を行う際によく使われているpickleモジュールを紹介した。次回は、オブジェクトを直列化してファイルに保存するためによく使われているもう1つのモジュールであるshelveモジュールを紹介する。
Copyright© Digital Advantage Corp. All Rights Reserved.