12.1. pickle — Python オブジェクトの直列化

ソースコード: Lib/pickle.py


pickle モジュールは Python オブジェクトの直列化および直列化されたオブジェクトの復元のためのバイナリプロトコルを実装しています。“Pickle 化” は Python オブジェクト階層をバイトストリームに変換する処理、“非 pickle 化” は (バイナリファイル または バイトライクオブジェクト から) バイトストリームをオブジェクト階層に復元する処理を意味します。pickle 化 (および非 pickle 化) は “直列化 (serialization)”、”整列化 (marshalling)”、あるいは [1] “平坦化 (flattening)” とも呼ばれますが、混乱を避けるため、ここでは “Pickle 化”、”非 pickle 化” で統一します。

警告

pickle モジュールはエラーや不正に生成されたデータに対して安全ではありません。信頼できない、あるいは認証されていないソースから受け取ったデータを非 pickle 化してはいけません。

12.1.1. 他の Python モジュールとの関係

12.1.1.1. marshal との比較

Python には marshal と呼ばれるより原始的な直列化モジュールがありますが、一般的に Python オブジェクトを直列化する方法としては pickle を選ぶべきです。 marshal は基本的に .pyc ファイルをサポートするために存在しています。

pickle モジュールはいくつかの点で marshal と明確に異なります:

  • pickle モジュールでは、同じオブジェクトが再度直列化されることのないよう、すでに直列化されたオブジェクトについて追跡情報を保持します。 marshal はこれを行いません。

    この機能は再帰的オブジェクトと共有オブジェクトの両方に重要な関わりをもっています。再帰的オブジェクトとは自分自身に対する参照を持っているオブジェクトです。再帰的オブジェクトは marshal で扱うことができず、実際、再帰的オブジェクトを marshal 化しようとすると Python インタプリタをクラッシュさせてしまいます。共有オブジェクトは、直列化しようとするオブジェクト階層の異なる複数の場所で同じオブジェクトに対する参照が存在する場合に生じます。共有オブジェクトを共有のままにしておくことは、変更可能なオブジェクトの場合には非常に重要です。

  • marshal はユーザ定義クラスやそのインスタンスを直列化するために使うことができません。 pickle はクラスインスタンスを透過的に保存したり復元したりすることができますが、クラス定義をインポートすることが可能で、かつオブジェクトが保存された際と同じモジュールで定義されていなければなりません。

  • marshal の直列化フォーマットは Python の異なるバージョンで可搬性があることを保証していません。 marshal の本来の仕事は .pyc ファイルのサポートなので、Python を実装する人々には、必要に応じて直列化フォーマットを以前のバージョンと互換性のないものに変更する権限が残されています。 pickle 直列化フォーマットには、全ての Python リリース間で以前のバージョンとの互換性が保証されています。

12.1.1.2. json との比較

pickle プロトコルと JSON (JavaScript Object Notation) との基本的な違いは以下のとおりです:

  • JSON はテキストの直列化フォーマット (大抵の場合 utf-8 にエンコードされますが、その出力は Unicode 文字列です) で、pickle はバイナリの直列化フォーマットです;

  • JSON は人間が読める形式ですが、pickle はそうではありません;

  • JSON は相互運用可能で Python 以外でも広く使用されていますが、pickle は Python 固有です;

  • JSON は、デフォルトでは Python の組み込み型の一部しか表現することができず、カスタムクラスに対しても行えません; pickle は極めて多くの Python 組み込み型を表現できます (その多くは賢い Python 内省機構によって自動的に行われます; 複雑なケースでは 固有のオブジェクト API によって対応できます)。

参考

json モジュール: JSON への直列化および復元を行うための標準ライブラリモジュール。

12.1.2. データストリームの形式

pickle によって使用されるデータフォーマットは Python 固有です。これは、JSON や XDR のような外部標準によって (例えばポインター共有を表わすことができないといったような) 制限を受けることがないという利点があります; ただし、これは非 Python プログラムが pickle された Python オブジェクトを再構成することができないということも意味します。

デフォルトでは、pickle データフォーマットは比較的コンパクトなバイナリ表現を使用します。サイズの抑制目的の最適化が必要なら、pickel されたデータを効率的に 圧縮する ことができます。

pickletools モジュールには pickle によって生成されたデータストリームを解析するためのツールが含まれます。pickletools のソースコードには、pickle プロトコルで使用される命令コードに関する詳細なコメントがあります。

現在 pickle 化には 5 種類のプロトコルを使用できます。より高いプロトコルを使用するほど、作成された pickle を読み込むためにより高い Python のバージョンが必要になります。

  • プロトコルバージョン 0 はオリジナルの「人間に判読可能な」プロトコルで、Python の初期のバージョンとの後方互換性を持ちます。

  • プロトコルバージョン 1 は旧形式のバイナリフォーマットで、これも Python の初期バージョンと互換性があります。

  • プロトコルバージョン 2 は Python 2.3 で導入されました。このバージョンでは 新方式のクラス のより効率的な pickle 化を提供しました。プロトコル 2 による改良に関する情報は PEP 307 を参照してください。

  • プロトコルバージョン 3 は Python 3.0 で追加されました。このバージョンで bytes オブジェクトをサポートしました。これは Python 2.x では非 pickle 化できません。これはデフォルトのプロトコルで、他の Python 3 バージョンとの互換性が求められる場合の推奨プロトコルです。

  • プロトコルバージョン 4 は Python 3.4 で追加されました。このバージョンでは巨大なオブジェクトのサポート、より多くの種類のオブジェクトの pickle 化、および一部のデータ形式の最適化が行われました。プロトコル 4 による改良に関する情報は PEP 3154 を参照してください。

注釈

直列化は永続性より原始的な概念です。 pickle はファイルオブジェクトの読み書きを行いますが、永続オブジェクトの命名に関する問題にも、(さらに困難な) 永続オブジェクトへの並列アクセスに関する問題にも対応しません。pickle モジュールは複雑なオブジェクトをバイトストリームに変換し、バイトストリームから同じ内部構造のオブジェクトに復元することができます。これらのバイトストリームはファイルに出力されることが多いでしょうが、ネットワークを介して送信したり、データベースに格納することもありえます。shelve モジュールは、オブジェクトを DBM 方式のデータベースファイル上で pickle 化および非 pickle 化するシンプルなインターフェイスを提供します。

12.1.3. モジュールインタフェース

オブジェクト階層を直列化するには、dumps() 関数を呼ぶだけです。同様に、データストリームを復元するには、loads() 関数を呼びます。しかし、直列化および復元に対してより多くのコントロールを行いたい場合、それぞれ Pickler または Unpickler オブジェクトを作成することができます。

pickle モジュールは以下の定数を提供しています:

pickle.HIGHEST_PROTOCOL

利用可能なうち最も高い プロトコルバージョン (整数)。この値は protocol 値として関数 dump() および dumps() と、Pickler コンストラクターに渡すことができます。

pickle.DEFAULT_PROTOCOL

pickle 化で使用されるデフォルトの プロトコルバージョン (整数)。HIGHEST_PROTOCOL より低い値になる場合があります。現在のデフォルトプロトコルは Python 3 用に設計された 3 です。

この pickle 化の手続きを便利にするために、 pickle モジュールでは以下の関数を提供しています:

pickle.dump(obj, file, protocol=None, *, fix_imports=True)

obj を pickle 化し、すでにオープンしている ファイルオブジェクト file に書き込みます。Pickler(file, protocol).dump(obj) と等価です。

任意の引数 protocol は、整数で、pickle 化で使用するプロトコルを指定します; サポートされているプロトコルは 0 から HIGHEST_PROTOCOL までになります。指定されない場合、DEFAULT_PROTOCOL が使用されます。負数が与えられた場合、HIGHEST_PROTOCOL が使用されます。

引数 file は、1 バイトの引数一つを受け付ける write() メソッドを持たなければなりません。すなわち、file には、バイナリの書き込み用にオープンされたファイルオブジェクト、io.BytesIO オブジェクト、このインタフェースに適合するその他のカスタムオブジェクトをとることができます。

fix_imports が真であり、かつ、protocol が 3 未満の場合、pickle は新しい Python 3 の名前と Python 2 で使用されていた古いモジュール名との対応付けを試みるので、pickle データストリームは Python 2 でも読み込み可能です。

pickle.dumps(obj, protocol=None, *, fix_imports=True)

ファイルに書く代わりに、bytes オブジェクトとしてオブジェクトの pickle 表現を返します。

引数 protocol および fix_importsdump() では同じ意味になります。

pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict")

pickle オブジェクト表現を、オープンしている ファイルオブジェクト file から読み込み、その中で指定されているオブジェクト階層に再構成して返します。これは Unpickler(file).load() と等価です。

pickle のプロトコルバージョンは自動的に検出されます。したがって protocol 引数は必要ありません。pickle 化オブジェクト表現より後のバイト列は無視されます。

引数 file には 2 つのメソッド、整数引数をとる read() と、引数を要求しない readline() メソッドがなければなりません。両方のメソッドはバイト列を返す必要があります。そのため file には、バイナリ読み込みとしてオープンされたディスク上のファイル、io.BytesIO オブジェクト、あるいはこのインタフェースに適合するその他のカスタムオブジェクトなどを指定できます。

任意のキーワード引数 fix_importsencoding および errors は Python 2 で作成された pickle ストリームとの互換性を制御するために使用します。fix_imports が真の場合、pickle は古い Python 2 の名前と Python 3 で使用されている新しい名前との対応付けを試みます。encodingerrors は Python 2 で pickle 化された 8 ビット文字列インスタンスのデコード方法を指定します; デフォルトではそれぞれ ‘ASCII’ および ‘strict’ になります。8 ビット文字列インスタンスをバイト列オブジェクトとして読み込みたい場合には、encoding に ‘bytes’ を指定します。

pickle.loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict")

bytes オブジェクトから pickle 化されたオブジェクト階層を読み込んで、その中で指定されたオブジェクト階層に再構成して返します。

pickle のプロトコルバージョンは自動的に検出されます。したがって protocol 引数は必要ありません。pickle 化オブジェクト表現より後のバイト列は無視されます。

任意のキーワード引数 fix_importsencoding および errors は Python 2 で作成された pickle ストリームとの互換性を制御するために使用します。fix_imports が真の場合、pickle は古い Python 2 の名前と Python 3 で使用されている新しい名前との対応付けを試みます。encodingerrors は Python 2 で pickle 化された 8 ビット文字列インスタンスのデコード方法を指定します; デフォルトではそれぞれ ‘ASCII’ および ‘strict’ になります。8 ビット文字列インスタンスをバイト列オブジェクトとして読み込みたい場合には、encoding に ‘bytes’ を指定します。

pickle モジュールでは 3 つの例外を定義しています:

exception pickle.PickleError

他の pickle 化例外の共通基底クラス。Exception を継承しています。

exception pickle.PicklingError

Pickler が pickle 化不可能なオブジェクトに遭遇したときに送出されるエラー。PickleError を継承しています。

どんな種類のオブジェクトが pickle 化できるのか確認するには pickle 化、非 pickle 化できるもの を参照してください。

exception pickle.UnpicklingError

データ破損やセキュリティ違反のような、オブジェクトを非 pickle 化するのに問題がある場合に送出されるエラー。PickleError を継承します。

非 picke 化の最中に他の例外が送出されることもあるので注意してください。これには AttributeError, EOFError, ImportError, IndexError が含まれます (ただし必ずしもこれらに限定されません)。

pickle モジュールでは、2 つのクラス Pickler および Unpickler を提供しています:

class pickle.Pickler(file, protocol=None, *, fix_imports=True)

pickle 化されたオブジェクトのデータストリームを書き込むためのバイナリファイルを引数にとります。

任意の引数 protocol は、整数で、pickle 化で使用するプロトコルを指定します; サポートされているプロトコルは 0 から HIGHEST_PROTOCOL までになります。指定されない場合、DEFAULT_PROTOCOL が使用されます。負数が与えられた場合、HIGHEST_PROTOCOL が使用されます。

引数 file は、1 バイトの引数一つを受け付ける write() メソッドを持たなければなりません。すなわち、file には、バイナリの書き込み用にオープンされたファイルオブジェクト、io.BytesIO オブジェクト、このインタフェースに適合するその他のカスタムオブジェクトをとることができます。

fix_imports が真であり、かつ、protocol が 3 未満の場合、pickle は新しい Python 3 の名前と Python 2 で使用されていた古いモジュール名との対応付けを試みるので、pickle データストリームは Python 2 でも読み込み可能です。

dump(obj)

obj の pickle 化表現を、コンストラクターで与えられた、すでにオープンしているファイルオブジェクトに書き込みます。

persistent_id(obj)

デフォルトでは何もしません。このメソッドはサブクラスがオーバーライドできるように存在します。

persistent_id()None を返す場合、通常通り obj が pickle 化されます。それ以外の値を返した場合、Pickler がその値を obj のために永続的な ID として出力するようになります。この永続的な ID の意味は Unpickler.persistent_load() によって定義されています。persistent_id() によって返された値自身は永続的な ID を持つことができないことに注意してください。

詳細および使用例については 外部オブジェクトの永続化 を参照してください。

dispatch_table

pickler オブジェクトのディスパッチテーブルは copyreg.pickle() を使用して宣言できる種類の reduction functions のレジストリです。これはキーがクラスでその値が減少関数のマッピング型オブジェクトです。減少関数は関連するクラスの引数を 1 個とり、__reduce__() メソッドと同じインタフェースでなければなりません。

デフォルトでは、pickler オブジェクトは dispatch_table 属性を持たず、代わりに copyreg モジュールによって管理されるグローバルなディスパッチテーブルを使用します。しかし、特定の pickler オブジェクトによる pickle 化をカスタマイズするために dispatch_table 属性に dict-like オブジェクトを設定することができます。あるいは、Pickler のサブクラスが dispatch_table 属性を持てば、そのクラスのインスタンスに対するデフォルトのディスパッチテーブルとして使用されます。

使用例については ディスパッチテーブル を参照してください。

バージョン 3.3 で追加.

fast

廃止予定です。真値が設定されれば高速モードを有効にします。高速モードは、メモの使用を無効にします。それにより余分な PUT 命令コードを生成しなくなるので pickle 化処理が高速化します。自己参照オブジェクトに対しては使用すべきではありません。さもなければ Pickler に無限再帰を起こさせるでしょう。

よりコンパクトな pickle 化を必要とする場合は、pickletools.optimize() を使用してください。

class pickle.Unpickler(file, *, fix_imports=True, encoding="ASCII", errors="strict")

これは pickle データストリームの読み込みのためにバイナリファイルをとります。

pickle のプロトコルバージョンは自動的に検出されます。したがって protocol 引数は必要ありません。

引数 file には2つのメソッド、整数引数をとる read() メソッドと、引数を要求しない readline() 引数がなければなりません。両方のメソッドはバイトを返す必要があります。そのため file には、バイナリ読み込みのために開かれたディスク上のファイルオブジェクト、 io.BytesIO オブジェクト、あるいはこのインタフェースに適合するその他のカスタムオブジェクトなどを指定することが可能です。

任意のキーワード引数 fix_importsencoding および errors は Python 2 で作成された pickle ストリームとの互換性を制御するために使用します。fix_imports が真の場合、pickle は古い Python 2 の名前と Python 3 で使用されている新しい名前との対応付けを試みます。encodingerrors は Python 2 で pickle 化された 8 ビット文字列インスタンスのデコード方法を指定します; デフォルトではそれぞれ ‘ASCII’ および ‘strict’ になります。8 ビット文字列インスタンスをバイト列オブジェクトとして読み込みたい場合には、encoding に ‘bytes’ を指定します。

load()

コンストラクターで与えられたオープンしたファイルオブジェクトから pickle 化オブジェクト表現を読み込み、その中で指定されたオブジェクト階層に再構成して返します。pickle 化オブジェクト表現より後のバイト列は無視されます。

persistent_load(pid)

デフォルトで UnpicklingError を送出します。

もし定義されていれば、persistent_load() は永続的な ID pid によって指定されたオブジェクトを返す必要があります。永続的な ID が無効な場合、UnpicklingError を送出しなければなりません。

詳細および使用例については 外部オブジェクトの永続化 を参照してください。

find_class(module, name)

必要なら module をインポートして、そこから name という名前のオブジェクトを返します。ここで module および name 引数は str オブジェクトです。その名前が示唆することに反して find_class() は関数を探すためにも使われることに注意してください。

サブクラスは、どんな型のオブジェクトを、どのようにロードするか (潜在的にはセキュリティリスクの減少) に関する制御を得るためにこれをオーバーライドすることができます。詳細に関しては グローバル変数を制限する を参照してください。

12.1.4. pickle 化、非 pickle 化できるもの

以下の型は pickle 化できます:

  • NoneTrue 、および False

  • 整数、浮動小数点数、複素数

  • 文字列、バイト列、バイト配列

  • pickle 化可能なオブジェクトからなるタプル、リスト、集合および辞書

  • モジュールのトップレベルで定義された関数 (def で定義されたもののみで lambda で定義されたものは含まない)

  • モジュールのトップレベルで定義されている組込み関数

  • モジュールのトップレベルで定義されているクラス

  • __dict__ 属性を持つクラス、あるいは __getstate__() メソッドの返り値が pickle 化可能なクラス (詳細は クラスインスタンスの pickle 化 を参照)。

pickle 化できないオブジェクトを pickle 化しようとすると、PicklingError 例外が送出されます。この例外が起きたとき、すでに元のファイルには未知の長さのバイト列が書き込まれている場合があります。極端に再帰的なデータ構造を pickle 化しようとした場合には再帰の深さ制限を越えてしまうかもしれず、この場合には RecursionError が送出されます。この制限は、sys.setrecursionlimit() で慎重に上げていくことは可能です。

関数 (組込みおよびユーザー定義) は、値ではなく、”完全修飾” された名前参照で pickle 化されます。[2] これは関数が定義されたモジュールをともにした関数名のみが pickle 化されることを意味します。関数のコードやその属性は pickle 化されません。すなわち、非 pickle 化する環境で定義したモジュールがインポート可能な状態になっており、そのモジュール内に関数名のオブジェクトが含まれていなければなりません。この条件を満たさなかった場合は例外が送出されます。[3]

クラスも同様に名前参照で pickle 化されるので、unpickle 化環境には同じ制限が課せられます。クラス中のコードやデータは何も pickle 化されないので、以下の例ではクラス属性 attr が unpickle 化環境で復元されないことに注意してください

class Foo:
    attr = 'A class attribute'

picklestring = pickle.dumps(Foo)

pickle 化可能な関数やクラスがモジュールのトップレベルで定義されていなければならないのはこれらの制限のためです。

同様に、クラスのインスタンスが pickle 化された際、そのクラスのコードおよびデータはオブジェクトと一緒に pickle 化されることはありません。インスタンスのデータのみが pickle 化されます。この仕様は、クラス内のバグを修正したりメソッドを追加した後でも、そのクラスの以前のバージョンで作られたオブジェクトを読み出せるように意図的に行われています。あるクラスの多くのバージョンで使われるような長命なオブジェクトを作ろうと計画しているなら、そのクラスの __setstate__() メソッドによって適切な変換が行われるようにオブジェクトのバージョン番号を入れておくとよいかもしれません。

12.1.5. クラスインスタンスの pickle 化

この節では、クラスインスタンスがどのように pickle 化または非 pickle 化されるのかを定義したり、カスタマイズしたり、コントロールしたりするのに利用可能な一般的機構について説明します。

ほとんどの場合、インスタンスを pickle 化できるようにするために追加のコードは必要ありません。デフォルトで、pickle はインスタンスのクラスと属性を内省によって検索します。クラスインスタンスが非 pickle 化される場合、通常その __init__() メソッドは実行 されません 。デフォルトの振る舞いは、最初に初期化されていないインスタンスを作成して、次に保存された属性を復元します。次のコードはこの振る舞いの実装を示しています:

def save(obj):
    return (obj.__class__, obj.__dict__)

def load(cls, attributes):
    obj = cls.__new__(cls)
    obj.__dict__.update(attributes)
    return obj

クラスは、いくつかの特殊メソッドを提供することによって、デフォルトの振る舞いを変更することができます:

object.__getnewargs_ex__()

In protocols 2 and newer, classes that implements the __getnewargs_ex__() method can dictate the values passed to the __new__() method upon unpickling. The method must return a pair (args, kwargs) where args is a tuple of positional arguments and kwargs a dictionary of named arguments for constructing the object. Those will be passed to the __new__() method upon unpickling.

クラスの __new__() メソッドにキーワード引数のみ定義されている場合はこのメソッドを実装すべきです。そうしない場合、互換性のため __getnewargs__() メソッドの実装を推奨します。

バージョン 3.6 で変更: __getnewargs_ex__() is now used in protocols 2 and 3.

object.__getnewargs__()

This method serve a similar purpose as __getnewargs_ex__(), but supports only positional arguments. It must return a tuple of arguments args which will be passed to the __new__() method upon unpickling.

__getnewargs__() will not be called if __getnewargs_ex__() is defined.

バージョン 3.6 で変更: Before Python 3.6, __getnewargs__() was called instead of __getnewargs_ex__() in protocols 2 and 3.

object.__getstate__()

クラスはそのインスタンスをどう pickle 化するかについてさらに影響を与えることができます; クラスに __getstate__() メソッドが定義されていた場合それが呼ばれ、返り値のオブジェクトはインスタンスの辞書ではなく、インスタンスの内容が pickle 化されたものになります。__getstate__() がないときは通常通りインスタンスの __dict__ が pickle 化されます。

object.__setstate__(state)

非 pickle 化に際して、クラスが __setstate__() を定義している場合、それは非 pickle 化された状態とともに呼び出されます。その場合、状態オブジェクトが辞書でなければならないという要求はありません。そうでなければ、 pickle された状態は辞書で、その要素は新しいインスタンスの辞書に割り当てられます。

注釈

__getstate__() が偽値を返す場合、非 pickle 化時に __setstate__() メソッドは呼ばれません。

__getstate__() および __setstate__() メソッドの使い方に関する詳細な情報については 状態を持つオブジェクトの扱い 節を参照してください。

注釈

非 pickle 化時、__getattr__()__getattribute__() あるいは __setattr__() のような一部のメソッドがインスタンス上で呼び出されることがあります。この場合、これらのメソッドはいくつかの内部不変条件が真であることに依存しており、そのような不変条件を立証するために、データ型には __getnewargs__() または __getnewargs_ex__() が実装されていなければなりません; さもなくば、__new__()__init__() も呼び出されません。

これらから見るように、pickle は上記のメソッドを直接使用しません。実際には、これらのメソッドは __reduce__() 特殊メソッドを実装するコピープロトコルの一部です。コピープロトコルは、pickle 化とオブジェクトのコピーに必要な、データを取得するための統一されたインタフェースを提供します。 [4]

強力ですが、クラスに __reduce__() メソッドを直接実装することはエラーを起こしやすくなります。この理由のため、クラスの設計者は可能なかぎり高レベルインタフェース (__getnewargs_ex__()__getstate__() および __setstate__()) を使用するべきです。公開はしているものの、__reduce__() の使用は、あくまでオプションとして、より効果的な pickle 化につながる場合、あるいはその両方の場合のみにしてください。

object.__reduce__()

このインタフェースは現在、以下のように定義されています。 __reduce__() メソッドは引数を取らず、文字列あるいは (こちらの方が好まれますが) タプルのいずれかを返すべきです (返されたオブジェクトは、しばしば “reduce value” と呼ばれます)。

文字列が返された場合、その文字列はグローバル変数の名前として解釈されます。それはオブジェクトのモジュールから見たローカル名であるべきです; pickle モジュールは、オブジェクトのモジュールを決定するためにモジュールの名前空間を検索します。この振る舞いは、典型的にシングルトンで便利です。

タプルが返された場合、それは 2〜5 要素長でなければなりません。オプションのアイテムは省略することができます。あるいはそれらの値として None を渡すことができます。各要素の意味は順に:

  • オブジェクトの初期バージョンを作成するために呼ばれる呼び出し可能オブジェクト。

  • 呼出し可能オブジェクトに対する引数のタプル。呼出し可能オブジェクトが引数を受け取らない場合、空のタプルが与えられなければなりません。

  • 任意で、前述のオブジェクトの __setstate__() メソッドに渡されるオブジェクトの状態。オブジェクトがそのようなメソッドを持たない場合、値は辞書でなければならず、それはオブジェクトの __dict__ 属性に追加されます。

  • 任意で、連続した要素を yield する (シーケンスではなく) イテレーター。これらの要素は obj.append(item) を使用して、あるいはバッチでは obj.extend(list_of_items) を使用して、オブジェクトに追加されます。これは主としてリストのサブクラスに対して使用されますが、適切なシグネチャを持つ append() および extend() メソッドがあるかぎり、他のクラスで使用することもできます。 (append() または extend() のどちらが使用されるかは、どの pickle プロトコルバージョンが使われるかに加えて追加されるアイテムの数にも依存します。したがって、両方をサポートする必要があります)

  • 任意で、連続する key-value ペアを yield する (シーケンスでなく) イテレーター。これらの要素は obj[key] = value を使用して、オブジェクトに格納されます。これは主として辞書のサブクラスに対して使用されますが、__setitem__() を実装しているかぎり他のクラスで使用することもできます。

object.__reduce_ex__(protocol)

別の方法として、__reduce_ex__() メソッドを定義することもできます。唯一の違いは、このメソッドは単一の整数引数、プロトコルバージョンを取る必要があるということです。もし定義された場合、pickle は __reduce__() メソッドよりもこのメソッドを優先します。さらに、__reduce__() は自動的に拡張版の同義語になります。このメソッドの主な用途は、古い Python リリースに対して後方互換性のある reduce value を提供することです。

12.1.5.1. 外部オブジェクトの永続化

オブジェクトの永続化のために、pickle モジュールは、pickle データストリーム外のオブジェクトに対する参照の概念をサポートしています。そのようなオブジェクトは永続的 ID によって参照されます。それは、英数文字の文字列 (プロトコル 0 に対して) [5] あるいは単に任意のオブジェクト (より新しい任意のプロトコルに対して) のいずれかです。

そのような永続的 ID の分解能は pickle モジュールでは定義されていません; これはこの分解能を pickler および unpickler のそれぞれ persistent_id() および persistent_load() 上でのユーザー定義メソッドに移譲します。

外部の永続的 ID を持つ pickle オブジェクトの pickler は、引数にオブジェクトを取り、None かオブジェクトの永続的 ID を返すカスタム persistent_id() メソッドを持たなくてはなりません。None を返す場合、pickler は通常通りマーカーとともにオブジェクトを pickle 化するため、unpickler はそれを永続的 ID として認識します。

外部オブジェクトを非 pickle 化するには、unpickler は永続的 ID オブジェクトを取り被参照オブジェクトを返すカスタム persistent_load() メソッドを持たなくてはなりません。

これは、外部のオブジェクトを参照によって pickle 化するために永続的 ID をどのように使用するかを示す包括的な例です。

# Simple example presenting how persistent ID can be used to pickle
# external objects by reference.

import pickle
import sqlite3
from collections import namedtuple

# Simple class representing a record in our database.
MemoRecord = namedtuple("MemoRecord", "key, task")

class DBPickler(pickle.Pickler):

    def persistent_id(self, obj):
        # Instead of pickling MemoRecord as a regular class instance, we emit a
        # persistent ID.
        if isinstance(obj, MemoRecord):
            # Here, our persistent ID is simply a tuple, containing a tag and a
            # key, which refers to a specific record in the database.
            return ("MemoRecord", obj.key)
        else:
            # If obj does not have a persistent ID, return None. This means obj
            # needs to be pickled as usual.
            return None


class DBUnpickler(pickle.Unpickler):

    def __init__(self, file, connection):
        super().__init__(file)
        self.connection = connection

    def persistent_load(self, pid):
        # This method is invoked whenever a persistent ID is encountered.
        # Here, pid is the tuple returned by DBPickler.
        cursor = self.connection.cursor()
        type_tag, key_id = pid
        if type_tag == "MemoRecord":
            # Fetch the referenced record from the database and return it.
            cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),))
            key, task = cursor.fetchone()
            return MemoRecord(key, task)
        else:
            # Always raises an error if you cannot return the correct object.
            # Otherwise, the unpickler will think None is the object referenced
            # by the persistent ID.
            raise pickle.UnpicklingError("unsupported persistent object")


def main():
    import io
    import pprint

    # Initialize and populate our database.
    conn = sqlite3.connect(":memory:")
    cursor = conn.cursor()
    cursor.execute("CREATE TABLE memos(key INTEGER PRIMARY KEY, task TEXT)")
    tasks = (
        'give food to fish',
        'prepare group meeting',
        'fight with a zebra',
        )
    for task in tasks:
        cursor.execute("INSERT INTO memos VALUES(NULL, ?)", (task,))

    # Fetch the records to be pickled.
    cursor.execute("SELECT * FROM memos")
    memos = [MemoRecord(key, task) for key, task in cursor]
    # Save the records using our custom DBPickler.
    file = io.BytesIO()
    DBPickler(file).dump(memos)

    print("Pickled records:")
    pprint.pprint(memos)

    # Update a record, just for good measure.
    cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")

    # Load the records from the pickle data stream.
    file.seek(0)
    memos = DBUnpickler(file, conn).load()

    print("Unpickled records:")
    pprint.pprint(memos)


if __name__ == '__main__':
    main()

12.1.5.2. ディスパッチテーブル

pickle 化に依存する他のコードの邪魔をせずに、一部のクラスの pickle 化だけをカスタマイズしたい場合、プライベートのディスパッチテーブルを持つ pickler を作成することができます。

copyreg モジュールによって管理されるグローバルなディスパッチテーブルは copyreg.dispatch_table として利用可能です。したがって、copyreg.dispatch_table の修正済のコピーをプライベートのディスパッチテーブルとして使用することを選択できます。

例えば

f = io.BytesIO()
p = pickle.Pickler(f)
p.dispatch_table = copyreg.dispatch_table.copy()
p.dispatch_table[SomeClass] = reduce_SomeClass

これは SomeClass クラスを特別に扱うプライベートのディスパッチテーブルを持つ pickle.Pickler のインスタンスを作成します。あるいは、次のコード

class MyPickler(pickle.Pickler):
    dispatch_table = copyreg.dispatch_table.copy()
    dispatch_table[SomeClass] = reduce_SomeClass
f = io.BytesIO()
p = MyPickler(f)

も同じことをしますが、 MyPickler のすべてのインスタンスはデフォルトで同じディスパッチテーブルを共有します。 copyreg モジュールを使用する等価なコードは

copyreg.pickle(SomeClass, reduce_SomeClass)
f = io.BytesIO()
p = pickle.Pickler(f)

12.1.5.3. 状態を持つオブジェクトの扱い

ここでは、クラスを pickle 化する振る舞いの変更手順を紹介しています。TextReader クラスはテキストファイルをオープンし、readline() メソッドが呼ばれると、その度に行番号と行の内容を返します。TextReader インスタンスが pickle 化されるとき、ファイルオブジェクトメンバーを 除く すべての属性が保存されます。インスタンスが非 pickle 化されるとき、ファイルは再びオープンされ、最後に読み込んだ位置から読み込みを再開します。このような振る舞いを実装するには __setstate__() および __getstate__() メソッドを使用します。

class TextReader:
    """Print and number lines in a text file."""

    def __init__(self, filename):
        self.filename = filename
        self.file = open(filename)
        self.lineno = 0

    def readline(self):
        self.lineno += 1
        line = self.file.readline()
        if not line:
            return None
        if line.endswith('\n'):
            line = line[:-1]
        return "%i: %s" % (self.lineno, line)

    def __getstate__(self):
        # Copy the object's state from self.__dict__ which contains
        # all our instance attributes. Always use the dict.copy()
        # method to avoid modifying the original state.
        state = self.__dict__.copy()
        # Remove the unpicklable entries.
        del state['file']
        return state

    def __setstate__(self, state):
        # Restore instance attributes (i.e., filename and lineno).
        self.__dict__.update(state)
        # Restore the previously opened file's state. To do so, we need to
        # reopen it and read from it until the line count is restored.
        file = open(self.filename)
        for _ in range(self.lineno):
            file.readline()
        # Finally, save the file.
        self.file = file

使用例は以下のようになるでしょう:

>>> reader = TextReader("hello.txt")
>>> reader.readline()
'1: Hello world!'
>>> reader.readline()
'2: I am line number two.'
>>> new_reader = pickle.loads(pickle.dumps(reader))
>>> new_reader.readline()
'3: Goodbye!'

12.1.6. グローバル変数を制限する

デフォルトで、非 pickle 化は pickle データ内で見つけたあらゆるクラスや関数をインポートします。多くのアプリケーションでは、この振る舞いは受け入れられません。なぜなら、それによって unpickler が任意のコードをインポートして実行することが可能になるからです。この手の巧妙に作られた pickle データストリームがロードされたときに何を行うかをちょっと考えてみてください:

>>> import pickle
>>> pickle.loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
hello world
0

この例において、unpickler は os.system() 関数をインポートして、次に文字列の引数 “echo hello world” を適用しています。この例は無害ですが、システムを破壊する例を想像するのは難しくありません。

この理由のため、Unpickler.find_class() をカスタマイズすることで非 pickle 化で何を得るかを制御したくなるかもしれません。その名前が示唆するのと異なり、Unpickler.find_class() はグローバル (クラスや関数) が必要とした時にはいつでも呼びだされます。したがって、グローバルを完全に禁止することも安全なサブセットに制限することも可能です。

これは、一部の安全なクラスについてのみ builtins モジュールからロードすることを許可する unpickler の例です:

import builtins
import io
import pickle

safe_builtins = {
    'range',
    'complex',
    'set',
    'frozenset',
    'slice',
}

class RestrictedUnpickler(pickle.Unpickler):

    def find_class(self, module, name):
        # Only allow safe classes from builtins.
        if module == "builtins" and name in safe_builtins:
            return getattr(builtins, name)
        # Forbid everything else.
        raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
                                     (module, name))

def restricted_loads(s):
    """Helper function analogous to pickle.loads()."""
    return RestrictedUnpickler(io.BytesIO(s)).load()

この unpickler が働く使用例は次のように意図されます:

>>> restricted_loads(pickle.dumps([1, 2, range(15)]))
[1, 2, range(0, 15)]
>>> restricted_loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
Traceback (most recent call last):
  ...
pickle.UnpicklingError: global 'os.system' is forbidden
>>> restricted_loads(b'cbuiltins\neval\n'
...                  b'(S\'getattr(__import__("os"), "system")'
...                  b'("echo hello world")\'\ntR.')
Traceback (most recent call last):
  ...
pickle.UnpicklingError: global 'builtins.eval' is forbidden

この例が示すように、非 pickle 化を認めるものに注意しなければなりません。したがって、セキュリティが重要な場合は xmlrpc.client の marshal API や、サードパーティのソリューションのような別の選択肢を考慮した方がよいでしょう。

12.1.7. 性能

pickle プロトコルの最近のバージョン (プロトコル 2 以降) は一部の一般的な機能と組み込みデータ型を効率的にバイナリにエンコードするよう考慮されています。また、pickle モジュールは C 言語で書かれた透過的オプティマイザーを持っています。

12.1.8. 使用例

最も単純なコードでは、dump() および load() 関数を使用してください。

import pickle

# An arbitrary collection of objects supported by pickle.
data = {
    'a': [1, 2.0, 3, 4+6j],
    'b': ("character string", b"byte string"),
    'c': {None, True, False}
}

with open('data.pickle', 'wb') as f:
    # Pickle the 'data' dictionary using the highest protocol available.
    pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)

次の例は、pickle 化されたデータを読み込みます。

import pickle

with open('data.pickle', 'rb') as f:
    # The protocol version used is detected automatically, so we do not
    # have to specify it.
    data = pickle.load(f)

参考

copyreg モジュール

拡張型を登録するための Pickle インタフェース構成機構。

pickletools モジュール

pickle データの処理や分析を行うためのツール。

shelve モジュール

オブジェクトのインデクス付きデータベース; pickle を使います。

copy モジュール

オブジェクトの浅いコピーおよび深いコピー。

marshal モジュール

組み込み型の高性能な直列化。

脚注

[1]

marshal モジュールと間違えないように注意してください。

[2]

なぜ lambda 関数を pickle 化できないかというと、すべての lambda 関数は同じ名前: <lambda> を共有しているからです。

[3]

送出される例外は ImportErrorAttributeError になるはずですが、他の例外も起こりえます。

[4]

copy モジュールは、浅いコピーと深いコピーの操作にこのプロトコルを使用します。

[5]

英数文字に関する制限は、プロトコル 0 では永続的な ID が改行文字によって区切られるという事実によります。そのため、永続的な ID に何らかの改行文字が含まれると、結果として生じる pickle は判読不能になります。