Pythonの学習が進んでくると、プログラムを終了したあとも計算結果を残しておきたくなる場面が出てきます。学習済みのモデル、集計の途中結果、設定をまとめた辞書などです。
CSVやJSONに書き出す方法は知っているけれど、日付やクラスのインスタンスが混ざった途端に面倒になる。そんな経験はないでしょうか。
そこで登場するのがpickleです。Pythonのオブジェクトを、形を崩さずにそのまま保存できる標準ライブラリになります。
pickleは、オブジェクトをバイト列に変換する道具¶
pickleがやっていることは、意外とシンプルです。メモリ上のPythonオブジェクトを、保存や送信ができるバイト列に変換しているだけです。
この変換をシリアライズ、逆にバイト列からオブジェクトを組み立て直すことをデシリアライズと呼びます。pickleの世界では、それぞれpickle化、非pickle化という呼び方もします。
うれしいのは、辞書やリストだけでなく、日付やクラスのインスタンスまで扱えることです。JSONでは書き出せないオブジェクトも、pickleならそのまま保存して、そのまま戻せます。
たとえば日付を含む辞書をJSONにしようとすると、Object of type date is not JSON serializable というエラーで止まります。pickleならこれが通ります。
まずはdumpsとloadsで往復させてみる¶
理屈より先に、手を動かしたほうが早いです。追加のインストールは要らないので、importするだけで試せます。
覚える関数は4つだけです。バイト列にするdumps、戻すloads、ファイルに書くdump、ファイルから読むload。sがつくほうが文字列ならぬバイト列を相手にする、と考えると混乱しません。
import pickle
import datetime
data = {
"name": "佐藤",
"score": 88,
"joined": datetime.date(2026, 4, 1), # JSONでは扱えない型
}
# オブジェクト → バイト列
blob = pickle.dumps(data)
print(type(blob), len(blob)) # <class 'bytes'> 82
# バイト列 → オブジェクト
restored = pickle.loads(blob)
print(restored)
# {'name': '佐藤', 'score': 88, 'joined': datetime.date(2026, 4, 1)}
print(restored["joined"].year) # 2026(dateのまま戻っている)
注目してほしいのは最後の行です。文字列に化けたのではなく、dateオブジェクトとして復元されているので、そのまま年を取り出せます。
この復元力がpickleの持ち味です。自作クラスのインスタンスでも、同じように往復できます。
ファイルへ保存するときはバイナリモードで開く¶
ファイルに残す場合はdumpとloadを使います。ここで初心者がほぼ必ずつまずくのが、openのモード指定です。
pickleが書き出すのはテキストではなくバイト列なので、wbとrbを指定します。wやrで開くとTypeErrorになります。
import pickle
scores = {"数学": 88, "英語": 72}
# 書き込みは "wb"(バイナリ書き込み)
with open("scores.pkl", "wb") as f:
pickle.dump(scores, f)
# 読み込みは "rb"(バイナリ読み込み)
with open("scores.pkl", "rb") as f:
loaded = pickle.load(f)
print(loaded) # {'数学': 88, '英語': 72}
拡張子は.pklや.pickleがよく使われますが、決まりではありません。中身がバイナリなので、テキストエディタで開いても読めない点だけ覚えておいてください。
openのモード指定にまだ不安があるなら、先に基本を押さえておくと理解が速くなります。【関連記事】Pythonのファイル読み書きとは?open()とwith文の基本を初心者向けに解説
JSONとどう使い分けるのか¶
ここまで読むと、JSONは要らないのではと思うかもしれません。実際は逆で、外に出すデータはJSONのほうが安全で扱いやすい場面がほとんどです。
公式ドキュメントもこの2つを並べて比較しています。要点を表にまとめました。
| 見る場所 | pickle | json |
|---|---|---|
| 形式 | バイナリ。人が読めない | テキスト。人が読める |
| 扱える型 | Pythonのほとんどの型、自作クラスも可 | 一部の組み込み型のみ。自作クラスは不可 |
| 他の言語との連携 | Python専用 | どの言語からでも読める標準形式 |
| 信頼できないデータ | 読み込むだけで危険 | 読み込み自体でコードは動かない |
| 主な用途 | Python内部での一時保存やキャッシュ | API連携、設定ファイル、他システムとの受け渡し |
判断はこの一行に集約できます。自分のPythonプログラムだけで完結するならpickle、外の世界とやりとりするならJSONです。
APIとの連携でJSONを使う場面が多い方は、こちらも合わせて読んでおくと切り分けがはっきりします。【関連記事】PythonでJSONデータの扱いをマスター!API連携に必須のjsonモジュールの使い方
速さとサイズではpickleに分がある¶
用途が合っているときのpickleは、素直に速いです。手元の環境で10万件の整数リストを試したところ、変換にかかった時間も出来上がったデータの大きさも、JSONよりpickleのほうが小さくなりました。
数字そのものは環境で変わるので鵜呑みにはできませんが、傾向は納得できます。JSONは数値を文字に書き直すのに対し、pickleはバイナリのまま詰め込むからです。
大量のデータを何度も読み書きするキャッシュ用途で、pickleが選ばれる理由がここにあります。
pickleにも保存できないものがある¶
万能に見えるpickleですが、苦手なものもあります。代表格が、その場で作った無名関数と、外の世界とつながっている資源です。
無名関数をdumpsに渡すと、PicklingErrorで止まります。関数は中身のコードではなく、どのモジュールのどの名前かという情報として保存されるため、名前のない関数は記録しようがないのです。
開いたままのファイルオブジェクトも保存できず、こちらはTypeErrorになります。ネットワーク接続やデータベース接続も同じ理由で扱えません。
つまずきやすい組み合わせを整理しておきます。
| 対象 | 結果 |
|---|---|
| 数値・文字列・リスト・辞書 | 問題なく保存できる |
| datetimeやdateなどの標準の型 | 保存できる |
| 自作クラスのインスタンス | モジュールの階層から名前をたどれれば保存できる |
| lambdaや関数の中で定義した関数 | PicklingErrorになる |
| 開いているファイル・接続・ロック | TypeErrorになる |
自作クラスを保存するときは、クラス定義そのものが読み込む側にも必要になります。pickleが記録するのは名前だけで、クラスの中身までは持っていかないためです。
保存したいデータの入れ物を用意する段階なら、dataclassを使うと定義が短くなって見通しがよくなります。【関連記事】Pythonのdataclassとは?クラスの定義がぐっと短くなる書き方を初心者向けに解説
いちばん大事な注意点は、どこから来たデータかということ¶
ここからがこの記事の本題です。pickleには、使う前に必ず知っておくべき性質があります。
公式ドキュメントは冒頭で、pickleモジュールは安全ではないと明記しています。信頼できるデータだけを読み込むこと、そして信頼できない送信元から来たデータや改ざんされた可能性のあるデータを決して読み込まないこと、という警告です。
なぜそこまで強い言い方になるのか。loadやloadsが、単なるデータの読み込みでは終わらないからです。
pickleのデータには、復元のときに呼び出す関数と引数を書き込めます。この仕組みを悪用すると、読み込んだ瞬間に任意の処理を実行させられます。
危険さを実感するために、無害な例で確かめてみましょう。攻撃の再現ではなく、仕組みの確認が目的です。
import pickle
class Sample:
def __reduce__(self):
# 復元時に呼ぶ関数と引数を指定できる
return (print, ("読み込んだだけでこの行が動きました",))
blob = pickle.dumps(Sample())
# ただ読み込んだだけのつもりでも、print が実行される
pickle.loads(blob)
# 読み込んだだけでこの行が動きました
ここではprintを指定しましたが、この場所には別の関数も書けてしまいます。ダウンロードした.pklファイルや、外部から送られてきたバイト列をloadするのは、送られてきたプログラムを実行するのとほぼ同じ意味を持ちます。
私は10年ほどエンジニアとして開発に関わってきましたが、学習済みモデルの配布ファイルをpickleで受け渡ししている現場に何度も出会いました。配布元が信頼できるかどうかを確認せずに読み込む運用は、いま思い返しても危ういものでした。
改ざんを防ぎたいなら署名を添える¶
とはいえ、自分のシステム内でpickleを受け渡ししたい場面はあります。公式ドキュメントは、データが改ざんされていないことを保証したい場合の選択肢としてhmacによる署名を挙げています。
保存や送信のときに署名を付け、読み込む前に署名を検証する。検証に通ったものだけをloadする、という流れです。
署名や照合の考え方は、パスワード保存の話と地続きです。【関連記事】Pythonのhashlibとは?パスワードをそのまま保存してはいけない理由とハッシュの基本を初心者向けに解説
プロトコルという数字は気にしなくていい¶
pickleを調べていると、protocolという引数を見かけます。データの書き方の世代を表す数字で、0から5まであります。
普段は指定しなくて問題ありません。ただし、複数のPythonバージョンでファイルをやりとりするときだけ注意が必要です。
Python 3.8から3.13では標準の世代が4でしたが、Python 3.14では5に変わりました。新しいPythonで作ったファイルを古いPythonで読もうとすると、そこで失敗する可能性があります。
古い環境にも渡す予定があるなら、pickle.dumps(data, protocol=4)のように明示しておくと安心です。逆に言えば、同じ環境の中で完結する用途なら意識する必要はありません。
結局、いつ使えばいいのか¶
最後に判断の軸をまとめておきます。pickleが向いているのは、自分のPythonプログラムが自分のために作って自分で読み戻すデータです。
途中経過のキャッシュ、時間のかかる計算結果の一時保存、同じマシン内でのプロセス間のやりとり。このあたりが本来の出番になります。
反対に、外部に配布するデータ、他の言語から読むデータ、ユーザーから受け取るデータには使いません。ここはJSONや、用途によってはデータベースの仕事です。
長く残したい構造化データなら、ファイルではなくテーブルに入れてしまうほうが後々ラクになります。【関連記事】Pythonからデータベースを操作するsqlite3の使い方を解説
まとめ¶
pickleは、Pythonのオブジェクトをそのままの形で保存し、そのまま復元できる標準ライブラリです。dumpsとloads、dumpとloadの4つを押さえれば、基本の使い方は足ります。
ファイルを開くときはwbとrbを指定すること。JSONと違って人が読めるテキストにはならないこと。この2点が最初の関門です。
そして何より、信頼できないデータをloadしないこと。pickleの読み込みは、データを開く操作ではなくプログラムを動かす操作だと考えてください。
まずは自分の辞書をひとつ、dumpsとloadsで往復させてみてください。JSONでは通らない型がそのまま戻ってくる感覚が、いちばんの理解の近道になります。
参考情報¶
- pickle — Python object serialization(Python公式ドキュメント)
- What's New In Python 3.14(Python公式ドキュメント)
- PEP 574 – Pickle protocol 5 with out-of-band data
ここまでお読みいただきありがとうございました。