pickleを使ってみよう
pickleモジュール
Python の標準ライブラリ pickleモジュールが、シリアル化/非シリアル化の処理を行うためのものです。そのため、シリアル化できるオブジェクトは pickable オブジェクトと呼ばれます。
pickleモジュールは、任意のPythonオブジェクトを一連のバイトに変換するためのアルゴリズムを実装します。 このプロセスは、オブジェクトのシリアル化とも呼ばれます。 次に、オブジェクトを表すバイトストリームを送信または保存し、後で再構築して同じ特性を持つ新しいオブジェクトを作成できます。
もっと簡単に言うと、pickleモジュールは、Pythonプログラムにデータの永続性を簡単に追加するための便利な方法を提供しているということです。その内部でどう処理処理されているのかは、ほとんどの人にとって理解する必要はありません。しかし、実際に使用することはとても簡単です。
注意:pickleモジュールはセキュリティー保証を提供しません。
実際、データのPickle化を解除(非Pickle化)すると、任意のコードが実行される可能性があります。 プロセス間通信またはデータストレージにpickleを使用する場合は注意が必要です。
また、安全であると確認できないデータは信頼しないようにしてください。 Pickle化されたデータソースのソースを検証する安全な方法の例については、メッセージ認証のための鍵付きハッシュ化を行う hmac モジュールを参照してください。 信頼できないデータを処理する場合は、jsonなどのより安全なシリアル化形式の方が適切な場合があります。 jsonとの比較 を参照してください。 文字列でのデータのエンコードとデコード
次のコードは、dumps()を使用してデータ構造を文字列としてエンコードしてから、その文字列をコンソールに出力します。 すべて組み込みの型で構成されるデータ構造を使用します。
code: pickle_string.py
import pickle
import pprint
print('DATA:', end=' ')
pprint.pprint(data)
data_string = pickle.dumps(data)
print('PICKLE: {!r}'.format(data_string))
デフォルトでは、pickleはPython3プログラム間で共有するときに最も互換性のあるバイナリ形式で記述されます。
code: bash
% python pickle_string.py
PICKLE: b'\x80\x03]q\x00}q\x01(X\x01\x00\x00\x00aq\x02X\x01\x00\x00\x00Aq\x03X\x01\x00\x00\x00bq\x04K\x02X\x01\x00\x00\x00cq\x05G@\x08\x00\x00\x00\x00\x00\x00ua.'
データがシリアル化された後、ファイル、ソケット、パイプなどに書き込むことができます。後でファイルを読み取り、データを非Pickle化して、同じ値で新しいオブジェクトを作成できます。
code: pickle_unpickle.py
import pickle
import pprint
print('BEFORE: ', end=' ')
pprint.pprint(data1)
data1_string = pickle.dumps(data1)
data2 = pickle.loads(data1_string)
print('AFTER : ', end=' ')
pprint.pprint(data2)
print('SAME? :', (data1 is data2))
print('EQUAL?:', (data1 == data2))
新しく構築されたオブジェクトは、元のオブジェクトと同じ値ですが、同じオブジェクトではありません。
code: bash
% python pickle_unpickle.py
SAME? : False
EQUAL?: True
ストリームの操作
dumps()とloads() に加えて、pickleはファイルのようなストリームを操作するための便利な関数を提供します。 ストリームに複数のオブジェクトを書き込んでから、書き込まれるオブジェクトの数や大きさを事前に知らなくても、ストリームからそれらを読み取ることができます。
code: pickle_stream.py
import io
import pickle
import pprint
class SimpleObject:
def __init__(self, name):
self.name = name
self.name_backwards = name::-1 return
data = []
data.append(SimpleObject('pickle'))
data.append(SimpleObject('preserve'))
data.append(SimpleObject('last'))
# ファイルをシミュレート
out_s = io.BytesIO()
# ストリームに書き出す
for o in data:
print('WRITING : {} ({})'.format(o.name, o.name_backwards))
pickle.dump(o, out_s)
out_s.flush()
# 読み込み可能なストリームを設定
in_s = io.BytesIO(out_s.getvalue())
# データを読み込む
while True:
try:
o = pickle.load(in_s)
except EOFError:
break
else:
print('READ : {} ({})'.format(
o.name, o.name_backwards))
この例では、2つのBytesIOバッファーを使用してストリームをシミュレートします。 最初のオブジェクトはpickle化されたオブジェクトを受け取り、その値はload()が読み取る2番目のオブジェクトに送られます。 単純なデータベース形式では、pickleを使用してオブジェクトを格納することもできます。 shalveモジュールはそのような実装の1つです。
code: bash
$ python pickle_stream.py
WRITING : pickle (elkcip)
WRITING : preserve (evreserp)
WRITING : last (tsal)
READ : pickle (elkcip)
READ : preserve (evreserp)
READ : last (tsal)
データの保存に加えて、ピクルスはプロセス間通信に便利です。 たとえば、os.fork()とos.pipe()を使用して、あるパイプからジョブ命令を読み取り、その結果を別のパイプに書き込むワーカープロセスを確立できます。 ジョブオブジェクトと応答オブジェクトは特定のクラスに基づいている必要がないため、ワーカープールを管理し、ジョブを送信して応答を受信するためのコアコードを再利用できます。 パイプまたはソケットを使用する場合は、各オブジェクトをダンプした後にフラッシュすることを忘れないでください。データを接続を介してもう一方の端にプッシュします。 再利用可能なワーカープールマネージャーについては、multiprocessingモジュールを参照してください。
オブジェクトの再構築に関する問題
カスタムクラスを操作する場合、pickle化されるクラスは、pickleを読み取るプロセスの名前空間に表示される必要があります。 クラス定義ではなく、インスタンスのデータのみがpickle化されます。 クラス名は、非Pickle化するときに新しいオブジェクトを作成するコンストラクターを見つけるために使用されます。 次の例では、クラスのインスタンスをファイルに書き込みます。
code: pickle_dump_to_file1.py
import sys
import pickle
class SimpleObject:
def __init__(self, name):
self.name = name
l = list(name)
l.reverse()
self.name_backwards = ''.join(l)
if __name__ == '__main__':
data = []
data.append(SimpleObject('pickle'))
data.append(SimpleObject('preserve'))
data.append(SimpleObject('last'))
with open(filename, 'wb') as out_s:
for o in data:
print('WRITING: {} ({})'.format(
o.name, o.name_backwards))
pickle.dump(o, out_s)
スクリプトを実行すると、コマンドラインで引数として指定された名前のファイルが作成されます。
code: bash
$ python pickle_dump_to_file1.py test.data
WRITING: pickle (elkcip)
WRITING: preserve (evreserp)
WRITING: last (tsal)
od コマンドで16進数と該当文字を表示してみます。
このファイルがバイナリファイルということの知るためで、ここでは内容の意味は重要ではありません。
code: bash
$ od -xc test.data| sed 10q
0000000 0380 5f63 6d5f 6961 5f6e 0a5f 6953 706d
200 003 c _ _ m a i n _ _ \n S i m p
0000020 656c 624f 656a 7463 710a 2900 7181 7d01
l e O b j e c t \n q \0 ) 201 q 001 }
0000040 0271 5828 0004 0000 616e 656d 0371 0658
q 002 ( X 004 \0 \0 \0 n a m e q 003 X 006
0000060 0000 7000 6369 6c6b 7165 5804 000e 0000
\0 \0 \0 p i c k l e q 004 X 016 \0 \0 \0
0000100 616e 656d 625f 6361 776b 7261 7364 0571
n a m e _ b a c k w a r d s q 005
このファイルから、pickle化されたオブジェクトをpickle.load()で読み出してみます。
code: pickle_load_from_file1.py
import sys
import pickle
import pprint
with open(filename, 'rb') as in_s:
while True:
try:
o = pickle.load(in_s)
except EOFError:
break
else:
print(f'READ: {o.name} ({o.name_backwards})')
実は、単純にロードすると失敗します。
code: bashg
$ python pickle_load_from_file1.py test.data
Traceback (most recent call last):
File "pickle_load_from_file1.py", line 10, in <module>
o = pickle.load(in_s)
AttributeError: Can't get attribute 'SimpleObject' on <module '__main__' from 'pickle_load_from_file1.py'>
これは、SimpleObjectクラスがpickle_load_from_file1.py では利用できないためです。
pickle_dump_to_file1.pyのスクリプトからSimpleObjectクラスをインポートするとうまくいくようになります。
修正バージョンは成功します。 このインポートステートメントをインポートリストの最後に追加すると、スクリプトはクラスを見つけてオブジェクトを作成できます。
code: pickle_load_from_file2.py
import sys
import pprint
import pickle
from pickle_dump_to_file1 import SimpleObject
with open(filename, 'rb') as in_s:
while True:
try:
o = pickle.load(in_s)
except EOFError:
break
else:
print(f'READ: {o.name} ({o.name_backwards})')
実行すると期待どおりの結果が得られます。
code: bash
$ python pickle_load_from_file2.py test.data
READ: pickle (elkcip)
READ: preserve (evreserp)
READ: last (tsal)
ここで重要なことは、オブジェクトが使用するクラスは、シリアル化されてダンプしたあとでロードするときにも必要になるということです。
Pickle化できないオブジェクト
すべてのオブジェクトをPickle化できるわけではありません。 オペレーティングシステムまたは別のプロセスに依存するランタイム状態のソケット、ファイルハンドル、データベース接続、およびその他のオブジェクトは、意味のある方法で保存できない場合があります。
pickle化/非pickle化できる型を示します。
None 、 True 、および False
整数、浮動小数点数、複素数
文字列、バイト列、バイト配列
pickle 化可能なオブジェクトからなるタプル、リスト、集合および辞書
モジュールのトップレベルで定義された関数
def で定義されたもののみで lambda で定義されたものは含まない
モジュールのトップレベルで定義されている組込み関数
モジュールのトップレベルで定義されているクラス
__dict__アトリビュートを持つクラス
__getstate__() メソッドの返り値が pickle 化可能なクラス
上記以外のpickle 化できないオブジェクトを pickle 化しようとすると、PicklingError 例外が送出されます。
Pickle化できないアトリビュートを持つオブジェクトは、__getstate__()および__setstate__()メソッドを定義して、インスタンスの状態のサブセットを返すことでPiockle化することができます。
__getstate__()メソッドは、オブジェクトの内部状態を含むオブジェクトを返す必要があります。 その状態を表す便利な方法の1つは辞書を使用することですが、値は任意のPickableオブジェクトにすることができます。 状態は保存され、オブジェクトがPickle化データからロードされるときに__setstate__()に渡されます。
code: pickle_state.py
import pickle
class State:
def __init__(self, name):
self.name = name
def __repr__(self):
return 'State({!r})'.format(self.__dict__)
class MyClass:
def __init__(self, name):
print('MyClass.__init__({})'.format(name))
self._set_name(name)
def _set_name(self, name):
self.name = name
def __repr__(self):
return 'MyClass({!r}) (computed={!r})'.format(
self.name, self.computed)
def __getstate__(self):
state = State(self.name)
print('__getstate__ -> {!r}'.format(state))
return state
def __setstate__(self, state):
print('__setstate__({!r})'.format(state))
self._set_name(state.name)
inst = MyClass('name here')
print('Before:', inst)
dumped = pickle.dumps(inst)
reloaded = pickle.loads(dumped)
print('After:', reloaded)
この例では、別のStateオブジェクトを使用して、MyClassの内部状態を保持しています。 MyClassインスタンスがPickle化されたデータからロードされると、__setstate__()には、オブジェクトの初期化に使用するStateインスタンスが渡されます。
code: bash
$ python pickle_state.py
MyClass.__init__(name here)
Before: MyClass('name here') (computed='ereh eman')
__getstate__ -> State({'name': 'name here'})
__setstate__(State({'name': 'name here'}))
After: MyClass('name here') (computed='ereh eman')
注意
戻り値が False の場合、オブジェクトの非Pickle化時に __setstate__()呼び出されません。
循環参照
pickleプロトコルはオブジェクト間の循環参照(Circular Reference)を自動的に処理するため、複雑なデータ構造は特別な処理を必要としません。 次図の有向グラフを考えてみましょう。 これにはいくつかのサイクルが含まれますが、正しい構造をピクルスにしてからリロードすることができます。
https://gyazo.com/a6af8833dcb48cbbd2aa0f5edc548129
Fig.1 循環参照を持つデータ構造
code: pickle_cycle.py
import pickle
class Node:
""" 簡単な有向グラフ """
def __init__(self, name):
self.name = name
self.connections = []
def add_edge(self, node):
""" このノードと他のノードの間にエッジを作成 """
self.connections.append(node)
def __iter__(self):
return iter(self.connections)
def preorder_traversal(root, seen=None, parent=None):
""" グラフのエッジを生成するジェネレータ関数 """
if seen is None:
seen = set()
yield (parent, root)
if root in seen:
return
seen.add(root)
for node in root:
recurse = preorder_traversal(node, seen, root)
for parent, subnode in recurse:
yield (parent, subnode)
def show_edges(root):
""" グラフのすべてのエッジを出力 """
for parent, child in preorder_traversal(root):
if not parent:
continue
print(f'{parent.name:>5} -> {child.name:>2} ({id(child)}')
# ノードを設定
root = Node('root')
a = Node('a')
b = Node('b')
c = Node('c')
# ノード間のエッジを追加
root.add_edge(a)
root.add_edge(b)
a.add_edge(b)
b.add_edge(a)
b.add_edge(c)
a.add_edge(a)
print('ORIGINAL GRAPH:')
show_edges(root)
# グラフをPickle化/非Pickle化して、新しいノードセットを作成
dumped = pickle.dumps(root)
reloaded = pickle.loads(dumped)
print('\nRELOADED GRAPH:')
show_edges(reloaded)
リロードされたノードは同じオブジェクトではありませんが、ノード間の関係は維持され、複数の参照を持つオブジェクトの1つのコピーのみがリロードされます。 これらのステートメントは両方とも、pickleを通過する前後のノードのid()の戻り値を調べることで検証できます。
code: bash
% python pickle_cycle.py
ORIGINAL GRAPH:
root -> a (140672613935952
a -> b (140672613936144
b -> a (140672613935952
b -> c (140672613946064
a -> a (140672613935952
root -> b (140672613936144
RELOADED GRAPH:
root -> a (140672614031248
a -> b (140672613946192
b -> a (140672614031248
b -> c (140672614052688
a -> a (140672614031248
root -> b (140672613946192
Pickle の内部操作を理解する
実は、Pickle化されたデータは、Pythonデータ構造を作成できる「プログラム」です。 これらのプログラムの作成には、制限付きスタック言語が使用されます。 つまり、if文やforループような制御を書くことはできません。それでも、これを理解することは、遅延処理などを行うためのコードを書くときにヒントになるはずです。
code: pickle_pik.py
import code
import pickle
import sys
sys.ps1 = "pik> "
sys.ps2 = "...> "
banner = "Pik -- The stupid pickle loader.\nPress Ctrl-D to quit."
class PikConsole(code.InteractiveConsole):
def runsource(self, source, filename="<stdin>"):
if not source.endswith(pickle.STOP.decode()):
return True # more input is needed
try:
print(repr(pickle.loads(source.encode())))
except:
self.showsyntaxerror(filename)
return False
pik = PikConsole()
pik.interact(banner)
ここで、pickle.STOPはモジュール変数として b'.'と bytes型で定義されています。
Python3 ではbytes型とstr型は明確に別の型になるため。このコードのようにsourceがstr型で定義されている場合では、次のように相互変化します。
code: python
source = 'Pythonセミナー'
# str -> bytes
bytes_data = source.encode()
print(bytes_data)
# bytes -> str
str_data = bytes_data.decode()
print(str_data)
code: bash
% python str_bytes.py
b'Python\xe3\x82\xbb\xe3\x83\x9f\xe3\x83\x8a\xe3\x83\xbc'
Pythonセミナー
さて、pickle_pik.py をpython で実行してみましょう。
code: bash
% python pickle_pik.py
Pik -- The stupid pickle loader.
Press Ctrl-D to quit.
pik>
Python の REPL のプロンプトが変わって、入力待ちの状態になりました。
ここで、空のリストを作成してみます。
これには、空のリストを表すシンボル]とpickle.STOPつまりドット(.)を入力します。
code: python
pik> ].
[]
空のリストが作成されて、それが表示されました。
同様に、辞書とタプルを作成することもできます。
code: python
pik> }.
{}
pik> ).
()
すべてのPickleストリームはドットで終わることに注意してください。 そのシンボルは、スタックから最上位のオブジェクトをポップして返します。 したがって、一連の整数を積み上げてストリームを終了するとします。 次に、結果は最後に入力したアイテムになります。
code: pyton
pik> I1
...> I2
...> I3
...> .
3
このコードでわかるように、整数はシンボルIで始まり、改行で終わります。
table: pickleでのデータ
モジュール変数 定義内容 意味
pickle.INT b'I' 整数
pickle.FLOAT b'F' 浮動小数点
pickle.STRING b'S' 文字列
pickle.UNICODE b'V' UNICODE文字列
pickle.TUTPLE b'T' タプル
pickle.LIST b'l' リスト 小文字のエル(L)
pickle.DICT b'd' 辞書
pickle.EMPTY_DICT b'}' 空の辞書
pick;e.EMPTY_TUPLE b')' 空のタプル
pickle.EMPTY_LIST b']' 空のリスト
pickle.EMPTY_SET b'\x8f' 空の集合
pickle.TRUE b'I01\n' TRUE
pickle.FALSE b'I00\n' FALSE
pickle.GLOBAL b'c' グローバル変数
pick;e.MARK b'(' マーカー
pickle.APPEND b'a' オブジェクトを追加
pickle.INST b'i' オブジェクトを挿入
pickle.SETITEM b's' 辞書/集合オブジェクトにオブジェクトを追加
pickle.PUT b'p' スタックの一番上のオブジェクトをメモにコピー
pickle.GET b'g' メモのオブジェクトをスタックの一番上に追加
pickle.REDUCE b'R' タプルを関数に適用
次に不動小数点や文字列を入力してみましょう。
code: python
pik> F1.0
...> .
1.0
pik> S'Python'
...> .
'Python'
基本を理解できたはずですので、もう少し複雑なもの、つまり複合オブジェクトの作成してみましょう。
code: python
pik> (I1
...> S'Python'
...> F2.0
...> t.
(1, 'Python', 2.0)
この例には、(とtの2つのシンボルがあります。(は単なるマーカーです。これは、タプルビルダーtにいつ停止するかを指示するスタック内のオブジェクトです。タプルビルダー マーカーに到達するまでスタックからアイテムをポップします。次に、これらのアイテムを使用してタプルを作成し、このタプルをスタックにプッシュします。複数のマーカーを使用して、ネストされたタプルを作成できます。
code: python
pik> (I1
...> (I2
...> I3
...> tt.
(1, (2, 3))
同様の方法を使用して、リストと辞書を作成します。
code: python
pik> (I0
...> I2
...> I3
...> l.
pik> (S'one'
...> I1
...> S'two'
...> I2
...> d.
{'one': 1, 'two': 2}
pik> (S'false'
...> I00
...> S'true'
...> I01
...> d.
{'false': False, 'true': True}
リストや辞書だからといってもマーカーは(であることに注目してください。
2度目の辞書の入力では、整数0と1のように見えますが、余分なゼロがあります。
辞書はアイテムがキーと値のペアでパックされるため、TrueとFalseになります。
タプルと同様に、リストと辞書をネストできます。
code: python
pik> ((I1
...> I2
...> t(I3
...> I4
...> ld.
リストまたは辞書を作成する別の方法があります。 マーカーを使用して複合オブジェクトを区切る代わりに、空のオブジェクトを作成し、それに何かを追加します。
code: python
pik> ]I0
...> aI1
...> aI2
...> a.
シンボルaは追加(append)を意味します。 アイテムとリストをポップして、アイテムをリストに追加します。 そして最後に、リストをスタックにプッシュします。 この方法でネストされたリストを作成する方法は次のとおりです。
code: python
pik> ]I0
...> a]I1
...> aI2
...> aa.
なんだか判りにくいという場合は、つぎをみてください。
code: python
pik> (lI0
...> a(lI1
...> aI2
...> aa.
空のリストのシンボル]を使用する代わりに、マーカーの直後にリストビルダー(l)を使用して、空のリストを作成しました。 これは、Picklerオブジェクトがオブジェクトをダンプするときにデフォルトで使用する表記法です。
リストと同様に、辞書は同様の方法を使用して作成できます。
code: python
pik> }S'one'
...> I1
...> sS'two'
...> I2
...> s.
{'one': 1, 'two': 2}
ただし、アイテムを辞書に設定するには、シンボルaではなくシンボルsを使用します。 「a」とは異なり、単一のアイテムではなく、キーと値のペアを取ります。
再帰的なデータ構造を構築することもできます。
code: python
pik> (Vpython
...> lp0
...> g0
...> a.
この例ではレジスター(pickleではメモと呼ばれる)を使用しています。シンボルpのプット(put)は、スタックの一番上の項目をメモにコピーします。 ここでは、メモの名前に0を使用していますが、それは何でもかまいません。 アイテムを元に戻すには、シンボルgを使用します。 メモからアイテムをコピーして、スタックの一番上に置きます。
では、集合はどうするのでしょうか? セットを構築するための特別な表記法がないため、単純ではなくなります。集合を作成する唯一の方法は、リスト(またはタプル)で組み込み関数set()を呼び出すことです。
code: python
pik> cbuiltins
...> set
...> ((S'a'
...> S'a'
...> S'b'
...> ltR.
{'a', 'b'}
シンボルcは、モジュールからオブジェクトを取得してスタックに配置します。 また、シンボルRはタプルを関数に適用します。 同じセマンティクスでも、シンボルRはスタックからタプルと関数をポップし、結果をスタックにプッシュします。 したがって、上記の例は、次のPythonコードとほぼ等価です。
code: python
import builtins
あるいはスター表記を用いた例。
code: python
import builtins
これは、set()を直接呼び出すのと同じです。
code: python
結局のところ、次のコードと同じになります。
code: python
{'a', 'a', 'b'}
例えば与えた数値を加算するような場合は次のようにかけます。
code: pyton
pik> cbuiltins
...> sum
...> ((I1
...> I5
...> ltR.
6
シンボルtとRにより、標準ライブラリから任意のコードを実行できることになります。
次のコードはLinux系プラットフォームの date コマンドをPickeストリームから実行している例です。
code: python
pik> cos
...> system
...> (S'date'
...> tR.
Thu Jan 20 20:17:09 JST 2021
0
データを削除するコマンドをPickleデータに簡単に挿入できるので、 悪意のある人が、それを利用する可能性があります。
これが、安全だと確認できないPickleデータは信頼してはいけない理由です。
組み込み関数map()とrange()を組み合わせることで暗黙的なループを記述することができます。
code: python
pik> cbuiltins
...> map
...> (cmath
...> sqrt
...> cbuiltins
...> range
...> (I1
...> I10
...> tRtR.
<map object at 0x7fc360376c90>
しかし、Pickleではジェネレータオブジェクトを取り出すことができないためうまくありません。こうした処理は関数として定義しておき、モジュールからロードして使用するという方法があります。
code: my_math.py
from math import sqrt
def my_math(a, b):
for v in map(sqrt, range(a, b)):
print(v)
code: python
pik> cmy_math
...> my_math
...> (I1
...> I10
...> tR.
1.0
1.4142135623730951
1.7320508075688772
2.0
2.23606797749979
2.449489742783178
2.6457513110645907
2.8284271247461903
3.0
None
同じ方法をif文にも提供することができます。つまりif文を模倣する関数を定義して、モジュールからロードすることができます。
code: my_if.py
def my_if(cond, then_val, else_val):
if cond:
return then_val
else:
return else_val
code: python
>> from my_if import my_if
>> my_if(True, 1, 0)
1
>> my_if(False, 1, 0)
0
code: python
pik> cmy_if
...> my_if
...> (I01
...> I1
...> I2
...> tR.
1
pik> cmy_if
...> my_if
...> (I00
...> I1
...> I2
...> tR.
2
ただし、それを再帰と組み合わせたときは、注意しないといくつかの問題が発生します。
code: python
>> from my_if import my_if
>>
>> def factorial(n):
... return my_if(n == 1,
... 1, n * factorial(n - 1))
...
>> factorial(2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in factorial
File "<stdin>", line 3, in factorial
File "<stdin>", line 3, in factorial
File "<stdin>", line 2, in factorial
RecursionError: maximum recursion depth exceeded in comparison
この場合、次のようにすると期待どおりになります。
code: python
>> def my_if(cond, then_val, else_val):
... if cond:
... return then_val()
... else:
... return else_val()
...
>> def factorial(n):
... return my_if(n==1, lambda: 1, lambda: n * factorial(n-1))
...
>> factorial(2)
2
>> factorial(5)
120
そもそも、よほどの理由がなければ、再帰的なPickleストリームを作成することはないはずです。
pickletools を使用することで Pickleストリームの内容を逆アセンブルすることができます。pickleモジュールを利用するだけであれば、必要性が少ないのですが、
それでも。調べたい pickle ファイルが信頼できないソースから来たものであるときには、 pickletools は pickle のバイトコードを実行しないので、より安全を確認するために使う場合があるかもしれません。
code: bash
$ python -m pickle x.pickle
(1, 2)
$ python -m pickletools x.pickle
0: \x80 PROTO 3
2: K BININT1 1
4: K BININT1 2
6: \x86 TUPLE2
7: q BINPUT 0
9: . STOP
highest protocol among opcodes = 2
コマンドラインオプション
-a / --annotate: 注釈として短い命令コードの説明を各行に表示します。
-o / --output=<file>: 出力結果を書き込むファイル名。
-l, --indentlevel=<num>: 新しい MARK レベルのインデントに使われる空白の数。
-m, --memo: 複数のオブジェクトが逆アセンブルされたとき、逆アセンブリ間でメモを保持します。
-p, --preamble=<preamble>: 複数の pickle ファイルが指定されたとき、各逆アセンブリの前に与えられたプリアンブルを表示します。
cloudpickleモジュール
cloudpickle は、Python標準ライブラリのpickleでサポートされていないPythonコンストラクトをシリアル化することができます。
cloudpickleは、Pythonコードがネットワーク経由で送出され、データに近いリモートホストで実行されるクラスターコンピューティングで便利になります。
特に、cloudpickleは、__main__モジュールでインタラクティブに定義された関数とクラス、lambda関数もPickle化することができます。
cloudpickleは、同じバージョンのPython間でのオブジェクトを交換を想定しています。異なるPythonバージョンでの動作は保証されていません。
そのため、長期的なオブジェクトストレージとしてcloudpickleを使用することは予期しない不具合を招く恐れがあります。
cloudpickleの欠点は、標準ライブラリのpickleモジュールよりも遅くなる可能性があることです。特に、大規模なPython辞書またはリストではシリアル化の時間が最大100倍遅くなる可能性があることは留意してください。
インストール
cloudpickle は次のようにインストールします。
code: bash
$ pip install cloudpickle
cloudpickle の利用方法
cloudpickle は pickle と上位互換を持っていて、pickle ができることはすべて cloudpickle で処理することができます。
pickle ではlambda関数はシリアル化することができませんが、cloudpickle では問題なくシリアル化することができます。
code: cloudpickle_lambda.py
import pickle
import cloudpickle
squared = lambda x: x ** 2
pickled_lambda = cloudpickle.dumps(squared)
new_squared = pickle.loads(pickled_lambda)
val = new_squared(2)
print(val)
実行してみましょう。
code: bash
$ python cloudpickle_lamba.py
4
lambda関数で定義した squaredをcloudpickle がシリアル化したpickled_lambdaを、pickle で非シリアル化できていることが確認できます。
cloudpickle では、Pythonシェルセッション(__main__モジュール内)でインタラクティブに定義された関数もシリアル化することができます。
code: python
In 1: %load cloudpickle_main_module.py In 2: # %load cloudpickle_main_module.py ...: import pickle
...: import cloudpickle
...:
...: CONSTANT = 42
...: def my_function(data: int) -> int:
...: return data + CONSTANT
...:
...: pickled_function = cloudpickle.dumps(my_function)
...: depickled_function = pickle.loads(pickled_function)
...: # depickled_function
...: # depickled_function(43)
...:
Out3: <function __main__.my_function(data: int) -> int> In 4: depickled_function(43) 参考
Python 公式ドキュメント
hmac - メッセージ認証のための鍵付きハッシュ化