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

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

警告

pickle モジュールはエラーや不正に生成されたデータに対するセキュリティを考慮していません。信頼できない、あるいは認証されていないソースから受信したデータを unpickle してはいけません。

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化に使われるプロトコルには、以下の 4 種類があります。

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

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

  • プロトコルバージョン 2 は Python 2.3 で導入されました。それは、 new-style class のより効率的な pickle 化を提供します。 プロトコル 2 によってもたらされた改良に関する情報は PEP 307 を参照してください。

  • プロトコルバージョン 3 は Python 3 で追加されました。 bytes オブジェクトを明示的にサポートしており、 Python 2.x で unpickle することはできません。これは現在のデフォルトであると同時に推奨プロトコルです; 可能な場合は常にこのプロトコルを使用してください。

注釈

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

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

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

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

pickle.HIGHEST_PROTOCOL

有効なプロトコルのうち、最も大きいバージョン。この値は、 protocol として渡せます。

pickle.DEFAULT_PROTOCOL

pickle 化に使われるデフォルトプロトコル。 HIGHEST_PROTOCOL よりも小さな値かもしれません。現在のデフォルトプロトコルは 3 で、 Python 3.0 のために設計された新しいプロトコルです。

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

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

すでに開かれている file object file に、 obj を pickle 化したものを表現する文字列を書き込みます。 Pickler(file, protocol).dump(obj) と同じです。

オプションの protocol 引数は、 pickler に与えられたプロトコルを使用するように指示します; サポートされるプロトコルは 0, 1, 2, 3 です。デフォルトのプロトコルは 3 です; これは後方互換性のないプロトコルで、 Python 3.0 のために設計されました。

負のプロトコルバージョンを指定すると、サポートされる最も高いプロトコルバージョンが選択されます。使用されるプロトコルが高いほど、生成された pickle を読むためには、より新しい Python のバージョンが必要になります。

file 引数は、整数引数を一つとる write() メソッドを持たなければなりません。従って、 file としては、バイナリの書き込みのために開かれたファイルオブジェクト、 io.BytesIO オブジェクト、その他前述のインタフェースに適合する他のカスタムオブジェクトをとることができます。

If fix_imports is true and protocol is less than 3, pickle will try to map the new Python 3.x names to the old module names used in Python 2.x, so that the pickle data stream is readable with Python 2.x.

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

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

オプションの protocol 引数は、 pickler に与えられたプロトコルを使用するように指示します; サポートされるプロトコルは 0, 1, 2, 3 です。デフォルトのプロトコルは 3 です; これは後方互換性のないプロトコルで、 Python 3.0 のために設計されました。

負のプロトコルバージョンを指定すると、サポートされる最も高いプロトコルバージョンが選択されます。使用されるプロトコルが高いほど、生成された pickle を読むためには、より新しい Python のバージョンが必要になります。

If fix_imports is true and protocol is less than 3, pickle will try to map the new Python 3.x names to the old module names used in Python 2.x, so that the pickle data stream is readable with Python 2.x.

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

開かれた file object file から pickle されたオブジェクトの表現を読み込んで、そこで指定されたオブジェクト階層を再構成して返します。これは Unpickler(file).load() と等価です。

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

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

Optional keyword arguments are fix_imports, encoding and errors, which are used to control compatibility support for pickle stream generated by Python 2.x. If fix_imports is true, pickle will try to map the old Python 2.x names to the new names used in Python 3.x. The encoding and errors tell pickle how to decode 8-bit string instances pickled by Python 2.x; these default to ‘ASCII’ and ‘strict’, respectively.

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

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

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

Optional keyword arguments are fix_imports, encoding and errors, which are used to control compatibility support for pickle stream generated by Python 2.x. If fix_imports is true, pickle will try to map the old Python 2.x names to the new names used in Python 3.x. The encoding and errors tell pickle how to decode 8-bit string instances pickled by Python 2.x; these default to ‘ASCII’ and ‘strict’, respectively.

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

exception pickle.PickleError

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

exception pickle.PicklingError

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

どんな種類のオブジェクトが pickle 化できるのか知るためには 何を pickle 化したり unpickle 化できるのか? を参照してください。

exception pickle.UnpicklingError

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

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

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

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

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

オプションの protocol 引数は、 pickler に与えられたプロトコルを使用するように指示します; サポートされるプロトコルは 0, 1, 2, 3 です。デフォルトのプロトコルは 3 です; これは後方互換性のないプロトコルで、 Python 3.0 のために設計されました。

負のプロトコルバージョンを指定すると、サポートされる最も高いプロトコルバージョンが選択されます。使用されるプロトコルが高いほど、生成された pickle を読むためには、より新しい Python のバージョンが必要になります。

file 引数は、整数引数を一つとる write() メソッドを持たなければなりません。従って、 file としては、バイナリの書き込みのために開かれたファイルオブジェクト、 io.BytesIO オブジェクト、その他前述のインタフェースに適合する他のカスタムオブジェクトをとることができます。

If fix_imports is true and protocol is less than 3, pickle will try to map the new Python 3.x names to the old module names used in Python 2.x, so that the pickle data stream is readable with Python 2.x.

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

廃止されました。もし True 値が設定されれば高速モードを有効にします。高速モードは、メモの使用を無効にします。それにより余分な PUT opcodes を生成しなくなるので 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 オブジェクト、あるいはこのインタフェースに合致するその他のカスタムオブジェクトなどを指定することが可能です。

Optional keyword arguments are fix_imports, encoding and errors, which are used to control compatibility support for pickle stream generated by Python 2.x. If fix_imports is true, pickle will try to map the old Python 2.x names to the new names used in Python 3.x. The encoding and errors tell pickle how to decode 8-bit string instances pickled by Python 2.x; these default to ‘ASCII’ and ‘strict’, respectively.

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 化したり unpickle 化できるのか?

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

  • NoneTrue 、および False

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

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

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

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

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

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

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

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

(組み込みおよびユーザ定義の) 関数は、値ではなく “完全記述された” 参照名として pickle 化されるので注意してください。これは、関数の定義されているモジュールの名前と一緒と併せ、関数名だけが pickle 化されることを意味します。関数のコードや関数の属性は何も pickle化されません。従って、定義しているモジュールは unpickle 化環境で import 可能でなければならず、そのモジュールには指定されたオブジェクトが含まれていなければなりません。そうでない場合、例外が送出されます [2]

クラスも同様に名前参照で 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 または unpickle されるのかを定義したり、カスタマイズしたり、コントロールしたりするのに利用可能な一般的機構について記述します。

ほとんどの場合、インスタンスを pickle 化できるようにするために追加のコードは必要ありません。デフォルトで、 pickle はインスタンスのクラスと属性を内省によって検索します。クラスインスタンスが unpickle される場合、通常その __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__()

プロトコル 2とそれより新しいバージョンでは、 __getnewargs__() メソッドを実装するクラスは unpickle 時に __new__() メソッドに渡される値を指示することができます。 これは、そのクラスの __new__() メソッドが引数を要求するときにしばしば必要とされます。

object.__getstate__()

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

object.__setstate__(state)

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

注釈

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

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

注釈

unpickleするとき、 __getattr__(), __getattribute__(), __setattr__() といったメソッドがインスタンスに対して呼ばれます。これらのメソッドが何か内部の不変条件に依存しているのであれば、その型は __getnewargs__() を実装してその不変条件を満たせるようにするべきです。それ以外の場合、 __new__()__init__() も呼ばれません。

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

クラスで __reduce__() を直接実装することは、強力ではありますが間違いを犯しがちです。この理由で、可能な場合は常に、クラス設計者は高レベルのインタフェース (つまり __getnewargs__(), __getstate__(), __setstate__()) を使用すべきです。しかし、 __reduce__() を使用することが唯一のオプションであったり、より効率的な pickle 化が行えるような (あるいはその両方の) いくつかのケースを示しましょう。

object.__reduce__()

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

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

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

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

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

  • 任意で、前述のオブジェクトの __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 に対して) [4] あるいは単に任意のオブジェクト (より新しい任意のプロトコルに対して) のいずれかです。

そのような永続的 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. グローバル変数を制限する

デフォルトで、 unpickle 化は 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

この例が示すように、 unpickle を認めるものに注意しなければなりません。したがって、セキュリティが重要な場合は 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': set([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]

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

[3]

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

[4]

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