pickleモジュールを使用して、Pythonのオブジェクトを直列化/復元(pickle化/非pickle化、シリアライズ/デシリアライズ)する方法と、その際の注意点を紹介する。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
* 本稿は2021年6月1日に公開された記事をPython 3.12.2で動作確認したものです(確認日:2024年3月1日)。
import pickle
favs = ['beer', 'sake']
mydata = {'name': 'かわさき', 'age': 999, 'weight': 123.4, 'favs': favs}
# pickle化してファイルに書き込み
with open('pickled.pkl', 'wb') as f:
pickle.dump(mydata, f)
# 非pickle化
with open('pickled.pkl', 'rb') as f:
mydata2 = pickle.load(f)
favs2 = mydata['favs']
print(mydata2)
# 出力結果
# {'name': 'かわさき', 'age': 999, 'weight': 123.4, 'favs': ['beer', 'sake']}
print(f'mydata2 == mydata: {mydata2 == mydata}') # mydata2 == mydata: True
print(f'mydata2 is mydata: {mydata2 is mydata}') # mydata2 is mydata: False
# クラスのインスタンスのpickle化
class Foo:
def __init__(self, name, age):
self.name = name
self.age = age
foo = Foo('かわさき', 999)
with open('pickled.pkl', 'wb') as f:
pickle.dump(foo, f)
# クラスのインスタンスの非pickle化
del foo # インスタンスを削除
with open('pickled.pkl', 'rb') as f:
foo = pickle.load(f) # 復元
print(f'name: {foo.name}, age: {foo.age}') # name: かわさき, age: 999
# 関数オブジェクトとクラスオブジェクトのpickle化
def hello():
print('hello')
with open('pickled.pkl', 'wb') as f:
pickle.dump(Foo, f) # 一つのファイルに複数のオブジェクトをpickle化できる
pickle.dump(hello, f)
with open('pickled.pkl', 'rb') as f:
Bar = pickle.load(f) # FooクラスをBarクラスに復元
greet = pickle.load(f) # hello関数をgreet関数に復元
bar = Bar('bar', 101)
print(f'name: {bar.name}, age: {bar.age}') # name: bar, age: 101
greet() # hello
# Fooインスタンスの復元にはFooクラスが定義されている必要がある
foo = Foo('かわさき', 999)
with open('pickled.pkl', 'wb') as f:
pickle.dump(foo, f)
del Foo, foo # Fooクラスとそのインスタンスであるfooを削除
with open('pickled.pkl', 'rb') as f:
foo = pickle.load(f) # FooクラスがないのでAttributeError例外
class Foo: # 上とは別のFooクラスを定義してみる
def __init__(self, a, b):
self.a = a
self.b = b
with open('pickled.pkl', 'rb') as f:
foo = pickle.load(f) # 復元できてしまう
print(foo.a) # AttributeError例外(復元したfooにはa属性はない)
Pythonが標準で提供している「pickleモジュール」は、オブジェクトの直列化(シリアライズ)とその復元(デシリアライズ)を行うために使用できる。ここでいう直列化とはPythonのオブジェクトをバイト列に変換する処理のことで、復元とはバイト列をPythonのオブジェクトに変換する処理のことである。直列化によりバイト列に変換されたデータはバイナリファイルに保存したり、バイト列として他のプログラムにネットワークを介して送信したりできる。なお、pickleモジュールでオブジェクトを直列化することをpickle化、復元することを非pickle化と呼ぶ。
pickleモジュールを使って、pickle化を行うにはそのモジュールが提供するdump関数もしくはdumps関数を呼び出す。前者はpickle化されたオブジェクトがバイナリファイルへ書き込まれ、後者はpickle化された結果(バイト列)が戻り値となる。非pickle化にはload関数もしくはloads関数を呼び出す。前者はバイナリファイルからpickle化されたデータを読み込んで非pickle化するもので、後者はバイト列を受け取ってそれを非pickle化するものだ。
以下に基本的な構文を示す。
# pickle化
dump(obj, file, protocol=None)
dumps(obj, protocol=None)
# 非pickle化
load(file)
loads(data)
dump/dumps関数の第1引数にはpickle化するオブジェクトを指定する。dump関数では第2引数にpickle化した結果のバイト列を書き込むバイナリファイル(を表すファイルオブジェクト)を指定する。dump関数の第3引数とdumps関数の第2引数には、pickle化の際に使用するプロトコルのバージョンを指定する。省略時には、pickleモジュールのDEFAULT_PROTOCOL値が指定されたものと見なされる。
2021年5月現在、pickle化/非pickle化に使われるプロトコルにはバージョン0〜5の6種類があり、Python 3.0〜3.7ではDEFAULT_PROTOCOLの値は3、Python 3.8以降では4となっている。プロトコルバージョン5は大きなサイズのデータを、余計なメモリコピーを行うことなく高速にpickle化/非pickle化を実行するために使われるものだ(本稿では扱わない)。
load関数の第1引数には非pickle化するデータを格納しているバイナリファイル(を表すファイルオブジェクト)を、loads関数の第1引数には非pickle化するデータ(バイト列)を渡す。pickle化の時点で、使用しているプロトコルのバージョンが、pickle化されるデータストリームの先頭に書き込まれるため、load/loads関数ではこれを指定する必要はない。
Pythonのオブジェクトをpickle化してバイナリファイルに書き込むコードの例を以下に示す。
import pickle
favs = ['beer', 'sake']
mydata = {'name': 'かわさき', 'age': 999, 'weight': 123.4, 'favs': favs}
# pickle化してファイルに書き込み
with open('pickled.pkl', 'wb') as f:
pickle.dump(mydata, f)
ここでは文字列、整数値、浮動小数点数値、リスト、辞書をpickle化している(これらのオブジェクトがpickle化可能であることを意味している)。dumps関数を使って、バイト列に変換するなら次のようになる。
b = pickle.dumps(mydata)
print(b) # b'\x80\x04\x95O\x00……\x04beer\x94\x8c\x04sake\x94eu.'
上のコードを実行してバイナリファイルに書き込まれたデータからPythonのオブジェクトを復元するコードの例は次のようになる。
with open('pickled.pkl', 'rb') as f:
mydata2 = pickle.load(f)
favs2 = mydata['favs']
print(mydata2)
# 出力結果
# {'name': 'かわさき', 'age': 999, 'weight': 123.4, 'favs': ['beer', 'sake']}
バイト列へpickle化したものを復元するには次のようになる。
mydata3 = pickle.loads(b)
print(mydata3) # 上と同じ出力結果
以下を実行すると、復元されたオブジェクトは元のオブジェクトと同じ値を持つが、異なるオブジェクトであることが分かる。
print(f'mydata2 == mydata: {mydata2 == mydata}') # mydata2 == mydata: True
print(f'mydata2 is mydata: {mydata2 is mydata}') # mydata2 is mydata: False
Pythonのドキュメント「pickle 化、非 pickle 化できるもの」にはpickle化/非pickle化できるものとして以下が挙げられている。
これら以外のオブジェクト(例えば、ファイルオブジェクトなど)はpickle化できない。
def文で定義した関数(関数オブジェクト)やclass文で定義したクラス自身(クラスオブジェクト)もpickle化可能だ。ただし、関数やクラスのコードそのものがpickle化されるのではなく、完全修飾された名前参照(それが定義されているモジュール名と関数名またはクラス名だけ)がpickle化される点には注意すること。簡単にいうと、関数やクラスをpickle化した場合、それを非pickle化する環境にはその関数やクラスを定義しているモジュール(から対応する関数またはクラス)がインポートされている必要があるということだ。
例として、クラスを定義して、そのインスタンスをpickle化してみよう(関数やクラスのpickle化はその後で見る)。
class Foo:
def __init__(self, name, age):
self.name = name
self.age = age
foo = Foo('かわさき', 999)
with open('pickled.pkl', 'wb') as f:
pickle.dump(foo, f)
del foo
with open('pickled.pkl', 'rb') as f:
foo = pickle.load(f) # 復元
print(f'name: {foo.name}, age: {foo.age}') # name: かわさき, age: 999
このコードでは、Fooクラスを定義して、そのインスタンスfooを生成した後にpickle化している。その後、もともとのインスタンスを削除してから、バイナリファイルからデータを読み込んで非pickle化している(ここではFooクラスが定義されているので、問題なく非pickle化できている)。
Copyright© Digital Advantage Corp. All Rights Reserved.