[解決!Python]バイナリファイルを読み書きするには:pickle編解決!Python

pickleモジュールを使用して、Pythonのオブジェクトを直列化/復元(pickle化/非pickle化、シリアライズ/デシリアライズ)する方法と、その際の注意点を紹介する。

» 2024年03月01日 05時00分 公開
[かわさきしんじDeep Insider編集部]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「解決!Python」のインデックス

連載目次

* 本稿は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属性はない)


pickleモジュールとは

 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


pickle化できるもの

 Pythonのドキュメント「pickle 化、非 pickle 化できるもの」にはpickle化/非pickle化できるものとして以下が挙げられている。

  • None値、ブーリアン値(True/False)
  • 整数値、浮動小数点数値、複素数値
  • 文字列、バイト列(bytesオブジェクト)、バイト配列(bytearrayオブジェクト)
  • pickle化可能なオブジェクトだけを要素とするリスト、タプル、辞書、集合
  • モジュールトップレベルで定義された組み込み関数、関数(ラムダ式を除く)、クラス
  • __dict__属性の値がpickle化可能なクラスのインスタンス。または__getstate__メソッドの戻り値が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.

RSSについて

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

メールマガジン登録

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