連載
» 2019年10月04日 05時00分 公開

[Python入門]pickleモジュールによるオブジェクトの直列化Python入門(2/2 ページ)

[かわさきしんじ,Deep Insider編集部]
前のページへ 1|2       

pickle化できるもの

 pickleモジュールのドキュメントの「pickle 化、非 pickle 化できるもの」には、pickleモジュールを使ってpickle化が可能なオブジェクトが列挙されている。いかにも幾つかを示しておこう。

  • ブール値(True/False)、None値
  • 数値(整数、浮動小数点数など)
  • 文字列、bytesオブジェクト、bytearrayオブジェクト
  • pickle化できるものだけを含んだリスト、タプル、集合、辞書
  • モジュールのトップレベルで定義されている関数、クラス
  • クラスのインスタンス

 これら以外のオブジェクトは、pickle化の対象とはならず、pickle化しようとするとエラーが発生する。pickle化できない代表的なオブジェクトがファイルオブジェクトだ。そこで、これをpickle化してみよう。

myfile = open('mydata.pickle', 'rb')
pickle.dumps(myfile)

ファイルオブジェクトはpickle化できない

 実行結果を以下に示す。

実行結果 実行結果

 このように例外が発生した(ただし、発生する例外は場合によってさまざまだ)。

 また、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)

関数を非pickle化するには、それと同じ関数がその環境で定義されている必要がある

 このコードでは、変数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化は成功するか

 実行結果を以下に示す。

同名の別の関数が呼び出された 同名の別の関数が呼び出された

 今度は非pickle化は成功するが、最初のhello関数とは異なる関数がrestored_hello関数の内容となった。同様なことは(クラスのインスタンスではなく)クラスについてもいえる。このことから、pickle化と非pickle化を行う環境では、使っているクラスや関数などを一致させる必要があり、それはpickle化と非pickle化を行うプログラマーが考慮しなければならないことには注意しよう。

pickleモジュールを使う上での注意点

 pickleモジュールのドキュメントの冒頭部分には以下のような警告が書かれている。

pickleモジュールのドキュメント冒頭には警告が書かれている pickleモジュールのドキュメント冒頭には警告が書かれている

 最後に、これがどういう意味なのかを簡単に説明しておこう。

 まず、以下の内容のファイルに「insecure.pickle」というファイル名を付けて保存しておこう(環境によっては改行コードを変更する必要があるかもしれない)。よく見ると、その内容が想像できるかもしれないが、これについては後で見てみよう。

c__builtin__
exec
(S'def hello(to): print("hello " + to)'
tR0c__main__
hello
(S'world'
tR.


insecure.pickleファイル

 以下はJupyter Notebook環境のファイル一覧ページからファイルを新規作成して、そこに上の内容を記述して、「insecure.pickle」という名前で保存したところだ。

insecure.pickleファイルを作成したところ insecure.pickleファイルを作成したところ

 拡張子が「pickle」となっていることから分かるように、これは非pickle化が可能な内容のファイルを手書きで作成したものだ。というわけで、これを非pickle化してみよう。

myfile = open('insecure.pickle', 'rb')
restored_data = pickle.load(myfile)
myfile.close()

謎のpickleファイルを非pickle化してみる

 これを実行すると、次のようになる。

非pickle化によりPythonのコードが実行され、メッセージが表示された 非pickle化によりPythonのコードが実行され、メッセージが表示された

 何やら「hello world」という文字列が表示された。非pickle化に際して、「hello world」という文字列を表示するコードが実行されたということだ。

 詳しくは説明しないが、insecure.pickleファイルに書かれたpickle化されたバイト列の内容はPythonのexec関数を使ってhello関数を定義して、引数を'world'として、その関数を呼び出すというものだった。

 つまり、出所不明のpickle化されたファイルを、安易に非pickle化すると、意図しないままに何らかのコードが実行される可能性がある。そのため、pickleモジュールのドキュメントには「信頼できない(中略)ソースから受け取ったデータを非pickle化してはいけません」と書いてあるということだ。

 非pickle化を行う場合には、自分が作成しているデータや信頼できるソースから入手したものだけをその対象とするようにしよう。

まとめ

 今回はPythonでオブジェクトの直列化を行う際によく使われているpickleモジュールを紹介した。次回は、オブジェクトを直列化してファイルに保存するためによく使われているもう1つのモジュールであるshelveモジュールを紹介する。

今回のまとめ

  • オブジェクトをファイルなどに保存したり、別のマシンに送信したりする目的で、フォーマット変換することを「直列化」と呼ぶ
  • pickleモジュールはPythonオブジェクトをbytesオブジェクトに直列化するために使える
  • pickleでは、Pythonオブジェクトをbytesオブジェクトに変換することを「pickle化」と、bytesオブジェクトからPythonオブジェクトに復元することを「非pickle化」と呼ぶ
  • pickle化にはpickleモジュールのdump関数/dumps関数を使用する
  • 非pickle化にはpickleモジュールのload関数/loads関数を使用する
  • pickleモジュールでpickle化/非pickle化できるオブジェクトには制限がある
  • 出所不明のデータを非pickle化することには危険が伴うのでしないことが推奨されている

前のページへ 1|2       

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。