Pythonのpickleの使い方

Modified: | Tags: Python

Pythonの標準ライブラリのpickleを使うと、オブジェクトをバイナリとして書き込んで保存したり、バイナリを読み込んで元のオブジェクトを復元したりできる。

オブジェクトをバイナリに変換することをpickle化・直列化(シリアライズ)・整列化・平坦化、バイナリからオブジェクトを復元することを非pickle化・非直列化(デシリアライズ)などと呼ぶ。

最後に述べるように、信頼できないpickleを復元するのは危険なので注意。

なお、NumPyやpandasではデータをバイナリとして読み書きするための方法が個別に用意されている。

本記事のサンプルコードでは以下のようにpickleモジュールをインポートしている。標準ライブラリに含まれているので、追加のインストールは不要。

import pickle

pickleでオブジェクトをファイルに書き込み・読み込み

pickle.dump()

オブジェクトをpickle化してファイルとして保存するにはpickle.dump()を使う。

第一引数にpickle化するオブジェクト、第二引数にファイルオブジェクトを指定する。

ファイルオブジェクトは組み込み関数open()の第二引数modewbとしてファイルを開いて取得する。wは書き込み、bはバイナリを表す。

d = {'int': 100, 'bool': False, 'list': [0, 1, 2]}

with open('data/temp/my_dict.pkl', 'wb') as f:
    pickle.dump(d, f)

pickleファイルの拡張子は何でもよいが、.pkl.pickleが使われることが多い。

pickle.load()

pickleとして保存されたファイルを読み込み、オブジェクトを復元するにはpickle.load()を使う。

上述のpickle.dump()の例で作成したファイルを読み込む。

第一引数にファイルオブジェクトを指定する。open()の第二引数moderbとして開けばよい。rは読み込み、bはバイナリを表す。

with open('data/temp/my_dict.pkl', 'rb') as f:
    d_load = pickle.load(f)

print(d_load)
# {'int': 100, 'bool': False, 'list': [0, 1, 2]}

pickleでオブジェクトをバイト列に変換・復元

pickle.dumps()

オブジェクトをpickle化してバイト列bytesに変換するにはpickle.dumps()を使う。

b = pickle.dumps(d)
print(b)
# b'\x80\x04\x95&\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x03int\x94Kd\x8c\x04bool\x94\x89\x8c\x04list\x94]\x94(K\x00K\x01K\x02eu.'

print(type(b))
# <class 'bytes'>

pickle.loads()

pickle化したバイト列を元のオブジェクトに復元するにはpickle.loads()を使う。

上述のpickle.dumps()の例で作成したバイト列を読み込む。

d_loads = pickle.loads(b)
print(d_loads)
# {'int': 100, 'bool': False, 'list': [0, 1, 2]}

pickle化のプロトコル

Python3.13時点で、pickle化のプロトコルはバージョン0から5までの6種類がある。詳細は以下の公式ドキュメントを参照。

pickle.dump()の第三引数およびpickle.dumps()の第二引数protocolでプロトコルを指定できる。省略するとデフォルトプロトコルの4が使われる(Python3.13時点)。

pickle.load()pickle.loads()ではプロトコルが自動的に検出されるため、指定する必要はない。

復元時に使われるPythonのバージョンが古いとプロトコルに対応しておらず読み込めない可能性があるので注意。

なお、デフォルトプロトコルの4はPython3.4以降対応なので、よほど古い環境でない限りは問題ないと思われる。最新のプロトコルバージョン5はPython3.8以降対応。

pickleのメリット・デメリット

辞書やリストなどを組み合わせたデータはJSONとして読み書きすることもできる。

以下の例では、オブジェクトをJSON文字列に変換して再度読み込んでいる。元のオブジェクトが復元できていることが確認できる。

import json

d = {'int': 100, 'bool': False, 'list': [0, 1, 2]}

j = json.dumps(d)
d_json_loads = json.loads(j)
print(d_json_loads)
# {'int': 100, 'bool': False, 'list': [0, 1, 2]}

ただし、例えば集合setや複素数complexといった型のデータはJSONでは扱えない。

d['set'] = {0, 1, 2}
d['complex'] = 1 + 2j

# j = json.dumps(d)
# TypeError: Object of type set is not JSON serializable

pickleではそのような型のデータも処理できる。より多くのPythonの型を扱えるのがpickleのメリットの一つである。

b = pickle.dumps(d)
d_pickle_loads = pickle.loads(b)
print(d_pickle_loads)
# {'int': 100, 'bool': False, 'list': [0, 1, 2], 'set': {0, 1, 2}, 'complex': (1+2j)}

一方、JSONはテキスト形式で他のアプリケーションでも広く用いられるのに対して、pickleはPython固有のバイナリ形式であるため利用範囲が限られるというデメリットがある。

最後に述べるように、pickleには、非pickle化の際に任意のコードを実行できてしまうという脆弱性も存在する。

pickleの注意点

関数やクラス、そのインスタンスをpickle化する場合

Pythonでは関数やクラスもオブジェクトなのでpickle化できる。しかし、関数やクラスは名前(完全修飾名)のみがpickle化され、そのコードやデータはpickle化されない。

また、クラスのインスタンスのpickle化においても、インスタンスのデータは保持されるが、そのクラスのコードやデータが一緒にpickle化されることはない。

例として、以下のようなクラスとそのインスタンスを考える。

class MyClass:
    def my_func(self):
        print('This is MyClass.')

mc = MyClass()

mc.a = 100

pickle化したあと、同一のスクリプト内(元のクラスが定義されている状態)で非pickle化するとインスタンスが復元できる。

b = pickle.dumps(mc)
mc_loads = pickle.loads(b)

mc_loads.my_func()
# This is MyClass.

print(mc_loads.a)
# 100

元のクラスが定義されていない状態では、pickle.load()pickle.loads()はエラーとなる。以下の例ではdelでクラスの定義を削除しているが、別のスクリプトでpickleファイルを読み込む場合も同様。

del MyClass

# mc_loads = pickle.loads(b)
# AttributeError: Can't get attribute 'MyClass' on <module '__main__'>

元のクラスと同名のクラスを新たに定義すれば非pickle化できるが、異なるコードで関数などを定義するとそちらが使われてしまう。インスタンス変数の値は更新されない限りそのまま。

class MyClass:
    def my_func(self):
        print('This is the newly defined MyClass.')

mc_loads = pickle.loads(b)

mc_loads.my_func()
# This is the newly defined MyClass.

print(mc_loads.a)
# 100

このように、クラスのインスタンスをpickle化・非pickle化する場合は、同一のクラスが正しく定義されている必要がある。

なお、pickleモジュールには、クラスのインスタンスのpickle化を細かく制御する仕組みも用意されている。詳細は公式ドキュメントを参照。

非pickle化において任意のコードを実行できる脆弱性あり

公式ドキュメントで警告されているように、pickleモジュールには非pickle化の際に任意のコードを実行できる脆弱性がある。

警告: pickle モジュールは 安全ではありません 。信頼できるデータのみを非 pickle 化してください。

非 pickle 化の過程で任意のコードを実行する ような、悪意ある pickle オブジェクトを生成することが可能です。信頼できない提供元からのデータや、改竄された可能性のあるデータの非 pickle 化は絶対に行わないでください。 pickle --- Python オブジェクトの直列化 — Python 3.13.3 ドキュメント

出所不明の信頼できないデータを非pickle化するのは危険なので要注意。

関連カテゴリー

関連記事