Pythonのオブジェクトを一時的に保存し、別のPythonで再利用する

この記事は[Takumi Akashiro ひとり Advent Calendar 2020](https://qiita.com/advent-calendar/2020/takumi_akashiro "ひとり Advent Calendar 2020")の16日目の記事です。

遂に数日分あった当アドカレのストックも切れ、
朝に事前投稿できなくなるほど、締め切りに追われるようになりました！

追い込まれている感じが楽しいですね！

## 始めに

みなさまはPythonのオブジェクトを一時保存したいと思ったことはないでしょうか？

例えば、デバッグとか……いや、今はDCCツールならVSCodeでptvsd経由でデバッグするのがメジャー[^1]ですもんね……
例えば、Mayaで使った複雑な構造の変数を他プロセスのPythonで使いたいとか……これはギリギリありそうですね。
今日はこれを想定にやっていきましょう！

[^1]: 今年はほぼPythonに向き合ってないので、この方法すら時代遅れなのかもしれない……と恐怖しながら書いてます。

## シリアライズ とは

オブジェクト指向プログラミングにおいて、あるオブジェクトをファイルとして書き出すことをシリアライズ、と呼ぶそうです。

Wikipedia曰くは以下のように書かれています。

> [シリアライズ - Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA#%E7%9B%B4%E5%88%97%E5%8C%96)
> 第二の意味の直列化（ちょくれつか）は、オブジェクト指向プログラミングにおいて使われる用語で、ある環境において存在しているオブジェクトを、バイト列やXMLフォーマットに変換することをいう。より具体的には、そのオブジェクトの状態を表す変数（フィールド）と、場合によってはオブジェクトの種類（クラス）を表すなんらかの識別子を、ファイル化できるようなバイト列やXMLフォーマットの形に書き出すことをいう。これにより、オブジェクトの表すデータを、ファイルとしてセーブしたり、ネットワークで送信したりすることができるようになる。このようにして得られたバイト列やXMLフォーマットは、直列化復元ないしデシリアライズによって、元のオブジェクトに復元される。
> 
> また、オブジェクトを直列化してファイルなどの永続記憶に保存することを永続化という。`

…自分は単語自体は知っていましたが、今日の記事のために調べて、初めてオブジェクト指向の用語ってのは知りましたね。

そしてPythonでserializeを行ってくれる標準モジュールは……`pickle`です！！
とりあえず使っていきましょう！



## `pickle` を使ってみる

まずは保存してみましょう！

```python
#! python3.8
import pickle
import tempfile
import pathlib
import re

data = {"nemui": "nero", "ohayou": "oyasumi", "nanka": "kanka", "hoge": pathlib.Path("d:/"), "reg": re.compile("\D{2}\d{16}")}

with open(pathlib.Path(tempfile.gettempdir()) / 'sample.pickle', 'wb') as f:
    pickle.dump(data, f)

```

てきてますね、次はこれを読んでみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294868/85c5caf3-5a0c-def5-18ee-15ad7cbeb022.png)

```python
#! python3.8
import pickle
import tempfile
import pathlib

with open(pathlib.Path(tempfile.gettempdir()) / 'sample.pickle', 'rb') as f:
    data = pickle.load(f)

for k, v in data.items():
    print(f"\t{k}:\t{v} ({type(v)})")
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294868/903a8015-33b8-75b3-0fec-0677f77b178f.png)

ちゃんと変数を復元できていますし、Python3のdictの実装通りに順番も保持されてますね！
(実装側の都合で順番が保持される特性がついたわけだから、当然か……)

## Python3 で出力した pickle をPython2で読んでみる

さあ、これをPython2に持っていってみましょう！
アッ……今の私用PCにPython2が入ってねえ！

仕方ない、古いHoudini内で動かすか……
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294868/3a3eca67-b34b-c031-45d6-707646b6aae0.png)

tempの位置が違うんか、お前……
と茶番(?)はさておき、気を取り直して、今度こそPython2で実行してみましょう。

```python
#! python2
import pickle
import tempfile

with open(tempfile.gettempdir() + '/sample.pickle', 'rb') as f:
    data = pickle.load(f)

for k, v in data.items():
    print("\t{0}:\t{1} ({2})".format(k, v, type(v)))
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294868/5274e49e-8fa9-892a-28e6-74110a78660c.png)

「プロトコル ガ チガイマス」 とエラーが出てきます！

やったね！（？）

## 公式ドキュメントを読んでみる

困ったときは公式ドキュメントを読んでみましょう！

> [pickle --- Python オブジェクトの直列化 — Python 3.9.1 ドキュメント](https://docs.python.org/ja/3/library/pickle.html)
> 現在、`pickle`に使用できるプロトコルは6種類あります。
> 使用されるプロトコルが高ければ高いほど、生成された`pickle`を読むために必要なPythonの最新バージョンが必要になります。

> * プロトコルバージョン3はPython 3.0で追加されました。<br>これはbyteオブジェクトを明示的にサポートしており、Python 2.xではpickleを解除できません。

> * プロトコルバージョン4はPython 3.4で追加されました。<br>非常に大きなオブジェクトのサポート、より多くの種類のオブジェクトのピックリング、いくつかのデータフォーマットの最適化が追加されました。これはPython 3.8から始まるデフォルトのプロトコルです。プロトコル4による改善点についてはPEP 3154を参照してください。

> * プロトコルバージョン 5 は Python 3.8 で追加されました。<br>帯域外データのサポートと帯域内データの高速化が追加されました。プロトコル 5 の改良点については、PEP 574 を参照してください。

> (中略)

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

というわけなので、`pickle(data, f, protocol=2)`と置き換えると……

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294868/67524bc7-9d63-74b2-46b5-01ba122e752e.png)

アッ…そうだ…pathlibはPython2系にはなかった……
仕方ないので、pathlibにしてたパスをstringに置き換えると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294868/90b411c3-ae5f-33d9-eb11-98db7cf26acb.png)

python3の変数も無事、Python2系で読めましたね！
(もうPython2とか使わなくなるのに、このくだり要る？)


## 締め

自分もごくまれにしか使わないですが、まあまあ便利なのでお勧めです。

まあ、簡単な構造の変数だったり、速度を犠牲にしていいのであれば、
個人的には可読性の高いjsonの方が好みですけど。

トラブったときに対応しやすいですし。