Last active
April 7, 2016 14:19
-
-
Save Cartman0/33cd21bbc84ae3c07ea84884f8354ab3 to your computer and use it in GitHub Desktop.
Dive Into Python3 13章メモ(Pythonオブジェクトをシリアライズ)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"metadata": { | |
"toc": "true" | |
}, | |
"cell_type": "markdown", | |
"source": "# Table of Contents\n <p><div class=\"lev1\"><a href=\"#13章-Pythonオブジェクトをシリアライズ-1\"><span class=\"toc-item-num\">1 </span>13章 Pythonオブジェクトをシリアライズ</a></div><div class=\"lev2\"><a href=\"#飛び込む-1.1\"><span class=\"toc-item-num\">1.1 </span>飛び込む</a></div><div class=\"lev2\"><a href=\"#データをPickleファイルに保存する-1.2\"><span class=\"toc-item-num\">1.2 </span>データをPickleファイルに保存する</a></div><div class=\"lev2\"><a href=\"#データをPickleファイルから読み込む-1.3\"><span class=\"toc-item-num\">1.3 </span>データをPickleファイルから読み込む</a></div><div class=\"lev2\"><a href=\"#ファイルを使わずにPickle化する-1.4\"><span class=\"toc-item-num\">1.4 </span>ファイルを使わずにPickle化する</a></div><div class=\"lev2\"><a href=\"#バイト列と文字列が再び不快な姿を現す-1.5\"><span class=\"toc-item-num\">1.5 </span>バイト列と文字列が再び不快な姿を現す</a></div><div class=\"lev2\"><a href=\"#Pickleファイルをデバッグ-1.6\"><span class=\"toc-item-num\">1.6 </span>Pickleファイルをデバッグ</a></div><div class=\"lev2\"><a href=\"#Pythonオブジェクトを他の言語で読むためにシリアライズする-1.7\"><span class=\"toc-item-num\">1.7 </span>Pythonオブジェクトを他の言語で読むためにシリアライズする</a></div><div class=\"lev2\"><a href=\"#データをjsonファイルに保存-1.8\"><span class=\"toc-item-num\">1.8 </span>データをjsonファイルに保存</a></div><div class=\"lev2\"><a href=\"#Pythonのデータ型をjsonにマッピングする-1.9\"><span class=\"toc-item-num\">1.9 </span>Pythonのデータ型をjsonにマッピングする</a></div><div class=\"lev2\"><a href=\"#jsonがサポートしていないデータ型をシリアライズ-1.10\"><span class=\"toc-item-num\">1.10 </span>jsonがサポートしていないデータ型をシリアライズ</a></div><div class=\"lev3\"><a href=\"#JSONEncoderクラスを継承する場合-1.10.1\"><span class=\"toc-item-num\">1.10.1 </span>JSONEncoderクラスを継承する場合</a></div><div class=\"lev3\"><a href=\"#datetime-への変換-1.10.2\"><span class=\"toc-item-num\">1.10.2 </span>datetime への変換</a></div><div class=\"lev2\"><a href=\"#jsonファイルからデータを読み込む-1.11\"><span class=\"toc-item-num\">1.11 </span>jsonファイルからデータを読み込む</a></div><div class=\"lev2\"><a href=\"#jsonモジュールを使うときのまとめ-1.12\"><span class=\"toc-item-num\">1.12 </span>jsonモジュールを使うときのまとめ</a></div><div class=\"lev2\"><a href=\"#参考リンク-1.13\"><span class=\"toc-item-num\">1.13 </span>参考リンク</a></div>" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "- [Dive Into Python3 1章メモ](http://nbviewer.jupyter.org/urls/gist.githubusercontent.com/Cartman0/d54093a99b9254c81bf1123adacbc48a/raw/eedc90bbbfc14e259854f2e739fffeec4cb4d8f7/DiveIntoPython3_01.ipynb)\n- [Dive Into Python3 2章メモ(ネイティブデータ型)](http://nbviewer.jupyter.org/urls/gist.githubusercontent.com/Cartman0/988b51d8482ad9ade835bb07efdffb38/raw/784cf276b7cebe254e59f09dcea6f09eea760d38/DiveIntoPython3_02.ipynb)\n- [Dive Into Python3 3章メモ(内包表記)](http://nbviewer.jupyter.org/urls/gist.githubusercontent.com/Cartman0/183ec6f6c835f621106f7c27d215290a/raw/bd7677946d6400bbe6acd257df7fb9c5976c3320/DiveIntoPython3_03.ipynb)\n- [Dive Into Python3 4章メモ(文字列)](http://nbviewer.jupyter.org/urls/gist.githubusercontent.com/Cartman0/bb974f82e8a3fc74ac82c3c2a5b1a4f9/raw/63a80011c9391b451108c1a4dc804ec5ff125f34/DiveIntoPython3_04.ipynb)\n- [Dive Into Python3 5章メモ(正規表現)](http://nbviewer.jupyter.org/urls/gist.githubusercontent.com/Cartman0/b834807a0dabb1c458b87be2f333a5ca/raw/37d6f25f67e017a569e1f0b60a9fcbe49d50515f/DiveIntoPython3_05.ipynb?flush_cache=true)\n- [Dive Into Python3 6章メモ(クロージャとジェネレータ)](http://nbviewer.jupyter.org/urls/gist.githubusercontent.com/Cartman0/a8998f8f88c5578271495ada56cc4809/raw/4e9b8ef3543016a710f905215693694acd703549/DiveIntoPython3_06.ipynb?flush_cache=true)\n- [Dive Into Python3 7章メモ(クラスとイテレータ)](http://nbviewer.jupyter.org/urls/gist.githubusercontent.com/Cartman0/8eb511c3b2aa6cadad6f6d49666c9db2/raw/60863d1ce1c1e9e4cb336ad79a0e252807beb87c/DiveIntoPython3_07.ipynb?flush_cache=true)\n- [Dive Into Python3 8章メモ(高度なイテレータ)](http://nbviewer.jupyter.org/urls/gist.githubusercontent.com/Cartman0/9ed3037cbdac59af53c20c125aed806e/raw/46ee3c71d72ab7ee665cf0c10f8717734632b462/DiveIntoPython3_08.ipynb?flush_cache=true)\n- [Dive Into Python3 9章メモ(ユニットテスト)](http://nbviewer.jupyter.org/urls/gist.githubusercontent.com/Cartman0/1833320ab55ae5ea660ce0c635483d26/raw/46034833206366c8011594cbea2efce90168d54b/DiveIntoPython3_09.ipynb?flush_cache=true)\n- [Dive Into Python3 10章メモ(リファクタリング)](http://nbviewer.jupyter.org/gist/Cartman0/0c0af20eb95236da4da9547258fe2bb3?flush_cache=true)\n- [Dive Into Python3 11章メモ(ファイル)](http://nbviewer.jupyter.org/gist/Cartman0/152eacd23b793ecd3dbf5f706ae33e92?flush_cache=true)\n- [Dive Into Python3 12章メモ(XML)](http://nbviewer.jupyter.org/gist/Cartman0/87d2d00c5d64c66778f20d1d056e76c7?flush_cache=true)" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "# 13章 Pythonオブジェクトをシリアライズ" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "## 飛び込む" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "**シリアライズ** の概念は、\n保存したり、再利用したり、他の人に送信したいデータをメモリ上に持っているとしよう。\nこれは、そのデータをどのように保存したいのか、どのように再利用したいのか、だれに送りたいのかによって違う。\n多くのゲームは終了時に「セーブ」を行い、ゲームを再び起動したときに以前の場所から再開できるようになっている(実際には、ゲーム以外の多くのアプリケーションも似たようなことを行っている)。\n\nこの場合、ゲームの終了時にどこまで進んだかを記録するデータ構造がディスクに保存され、再び起動するときにディスクから読み込まれる必要がある。\nこのデータは、作成したのと同じアプリケーションによって読み込まれるだけで、ネットワーク越しに転送されたり、作成したアプリケーション以外によって読み込まれることは無いという想定で作られている。\n\nしたがって、相互運用性に関して考慮すべきことは、\n古いバージョンのプログラムが作成したデータを、新しいバージョンのプログラムが読み込めることを保証することに限られる。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "このような場合には `pickle`モジュールが使える。\nこれはPython標準ライブラリに含まれているのでいつでも利用できるし、Pythonインタプリタと同様に大部分がC言語で書かれているので処理も速い。\nそして、どんなに複雑なPythonのデータ構造でも保存することができる。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "pickleモジュールは何を保存できるのか?\n\n- Pythonがサポートするすべてのネイティブデータ型: ブール値・整数・浮動小数点数・複素数・文字列・bytesオブジェクト・バイト配列・None。\n- ネイティブデータ型の任意の組み合わせからなるリスト・タプル・辞書・集合。\n- ネイティブデータ型の任意の組み合わせからなるリスト・タプル・辞書・集合の自由な組み合わせからなるリスト・タプル・辞書・集合(Pythonがサポートする最大のネストレベルまでネストできる)\n- 関数・クラス・クラスのインスタンス(注意あり)。\n\nこれでも不十分な場合は、pickleモジュールを拡張することもできる。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "## データをPickleファイルに保存する" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "pickleモジュールはデータ構造を扱う。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "ここでやりたいことは、Atomフィードのエントリのような何か意味のあるデータを収めたPythonの辞書を作ること。\nこの辞書が様々なデータ型を格納している。\nこれらの値の内容については気にしなくて良い。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": " entry = {}", | |
"execution_count": 1, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "entry['title'] = 'Dive into history, 2009 edition'\nentry['article_link'] = 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition'\nentry['comments_link'] = None\nentry['internal_id'] = b'\\xDE\\xD5\\xB4\\xF8'\nentry['tags'] = ('diveintopython', 'docbook', 'html')\nentry['published'] = True", | |
"execution_count": 2, | |
"outputs": [] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`time`モジュールは、時刻上の1点(ミリ秒までの精度を持つ)を表現するためのデータ構造 (`time_struct`) と、そのデータ構造を操作するための関数を含んでいる。\n\n`strptime()`関数は、フォーマットされた文字列を受け取って\n`time_struct` に変換する。\nこの文字列はデフォルトの形式で表現されているが、フォーマットコードによって制御することもできる。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "import time\nentry['published_date'] = time.strptime('Fri Mar 27 22:20:42 2009')\nentry['published_date']", | |
"execution_count": 3, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1)" | |
}, | |
"execution_count": 3 | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "entry", | |
"execution_count": 4, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "{'article_link': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition',\n 'comments_link': None,\n 'internal_id': b'\\xde\\xd5\\xb4\\xf8',\n 'published': True,\n 'published_date': time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1),\n 'tags': ('diveintopython', 'docbook', 'html'),\n 'title': 'Dive into history, 2009 edition'}" | |
}, | |
"execution_count": 4 | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "これをファイルに保存する。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "import pickle\npickle", | |
"execution_count": 5, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "<module 'pickle' from 'C:\\\\Miniconda3\\\\lib\\\\pickle.py'>" | |
}, | |
"execution_count": 5 | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`open()`関数を使ってファイルを開く。\nファイルをバイナリモードで開くために、ファイルモードは`'wb'` に設定する。\nこれを`with`文で包むことで、\n保存が終わったときに自動的にファイルが閉じられるようにしておく。\n\n`pickle`モジュールの `dump()`関数は、\nシリアライズ可能なPythonのデータ構造を1つ受け取り、\nそれを最新のバージョンの `Pickle`プロトコルを用いて\nPython固有のバイナリ形式にシリアライズした上で、開いておいたファイルにそれを保存する。\n" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "with open('entry.pickle', 'wb') as f:\n pickle.dump(entry, f)\n", | |
"execution_count": 6, | |
"outputs": [] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "- pickleモジュールはPythonのデータ構造を受け取り、それをファイルに保存する。\n\n- これを行うために、このモジュールは「Pickleプロトコル」と呼ばれる形式を用いてデータ構造を「シリアライズ」する。\n\n- PickleプロトコルはPython固有のものであり、\n他の言語で使用できる保証は無い。\nPerl、php、Java、その他のいかなる言語であっても、\n`entry.pickle`ファイルを有効に使うことはできないだろう。\n\n- pickleモジュールは、Pythonのあらゆるデータ構造をシリアライズできるわけではない。\nPythonに新しい型が追加されるたびにPickleプロトコルには何度も変更が加えられてきているが、制限は依然として存在する。\n\n- これらの変更の結果として、Python自体の異なるバージョン間においても互換性の保証がない。新しいバージョンのPythonは、古いバージョンのシリアライズ形式をサポートするが、\n**古いバージョンのPythonは(新しい型をサポートしていないので)新しいシリアライズ形式をサポートしない**。\n\n- 特に指定しない限り、pickleモジュールの関数は、最新バージョンのPickleプロトコルを使用する。このおかげでシリアライズできるデータ型の範囲は最も広くなるのだが、その代償として、最新のPickleプロトコルをサポートしていない古いバージョンのPythonでは生成されるPickleファイルを読み込めなくなってしまう。\n\n- 最新バージョンのPickleプロトコルはバイナリ形式だ。Pickleファイルがバイナリモードで開かれていることを確認しよう。さもなければ、データは書き込み中に破損してしまうだろう。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "## データをPickleファイルから読み込む" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "pickleモジュールはバイナリ形式を使うので、Pickleファイルは常にバイナリモードで開かなければならない。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`pickle.load()`関数は、ストリームオブジェクトを受け取り、ストリームからシリアライズされたデータを読み込み、新しいPythonオブジェクトを作り、\nシリアライズされたデータを新しいPythonオブジェクトの中に再構成し、\nその新しいPythonオブジェクトを返す。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "with open('entry.pickle', 'rb') as f:\n entry_load = pickle.load(f)", | |
"execution_count": 7, | |
"outputs": [] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`entry_load`変数は、見覚えのあるキーと値を持つ辞書になった。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "entry_load", | |
"execution_count": 8, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "{'article_link': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition',\n 'comments_link': None,\n 'internal_id': b'\\xde\\xd5\\xb4\\xf8',\n 'published': True,\n 'published_date': time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1),\n 'tags': ('diveintopython', 'docbook', 'html'),\n 'title': 'Dive into history, 2009 edition'}" | |
}, | |
"execution_count": 8 | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`pickle.dump()` / `pickle.load()` のサイクルによって、元のデータ構造と等しい内容をもつ新たなデータ構造がもたらされる。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`entry.pickle`ファイルを開いて、シリアライズされたデータを新しい変数entry2に読み込む。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "with open('entry.pickle', 'rb') as f:\n entry2 = pickle.load(f) ", | |
"execution_count": 9, | |
"outputs": [] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "2つの辞書 `entry` と `entry2` が等しいことをPythonが認めている。\nこのシェルでは、空の辞書からスタートし、特定のキーに手作業で値を設定することでentryを組み上げた。\nそして、この辞書をシリアライズし、entry.pickleファイルに保存した。\nさらに、そのファイルからシリアライズされたデータを読み込んで、元のデータ構造の完全な複製を作成した。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "entry == entry2", | |
"execution_count": 10, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "True" | |
}, | |
"execution_count": 10 | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "等しいことと同一であることは違う。\n元のデータ構造の完全な複製を作った。\nこれは正しい。しかし、これはコピーに過ぎない。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "entry is entry2", | |
"execution_count": 11, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "False" | |
}, | |
"execution_count": 11 | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "ここで、\n- 'tags'キーの値がタプルであることと、\n- 'internal_id'キーの値がbytesオブジェクトである。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "entry2['tags']", | |
"execution_count": 12, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "('diveintopython', 'docbook', 'html')" | |
}, | |
"execution_count": 12 | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false, | |
"scrolled": true | |
}, | |
"cell_type": "code", | |
"source": "entry2['internal_id']", | |
"execution_count": 13, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "b'\\xde\\xd5\\xb4\\xf8'" | |
}, | |
"execution_count": 13 | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "## ファイルを使わずにPickle化する" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "Pythonオブジェクトをディスク上のファイルに直接シリアライズする方法を示していた。\nしかし、ファイルが必要ない場合や、ファイルを使わずにやりたい場合はどうすればいいのだろうか? \n**`pickle` は、メモリ上のbytesオブジェクトにシリアライズすることもできる**。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`pickle.dumps()`関数(関数名の末尾に's'が付いていることに注意)は、\n`pickle.dump()`関数と同様のシリアライズを行う。\nただし、ストリームオブジェクトを受け取って、\nディスク上のファイルにシリアライズしたデータを書き込む代わりに、\nただ単にシリアライズしたデータを返す。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "b = pickle.dumps(entry)", | |
"execution_count": 14, | |
"outputs": [] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "Pickleプロトコルはバイナリ形式を用いるので、\n`pickle.dumps()`関数は `bytes`オブジェクトを返す。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "type(b)", | |
"execution_count": 15, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "bytes" | |
}, | |
"execution_count": 15 | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`pickle.loads()`関数(再び、関数名の末尾に's'が付いていることに注意)は、\n`pickle.load()`関数と同様のデシリアライズを行う。\nただし、ストリームオブジェクトを受け取って、ファイルからシリアライズされたデータを読み込む代わりに、\n`pickle.dumps()`関数が生成するような、シリアライズされたデータを含んだbytesオブジェクトを受け取る。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "entry3 = pickle.loads(b)", | |
"execution_count": 16, | |
"outputs": [] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "元の辞書の完全な複製になる。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "entry3 == entry", | |
"execution_count": 17, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "True" | |
}, | |
"execution_count": 17 | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "## バイト列と文字列が再び不快な姿を現す" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "Pickleプロトコルが最初に考案されたのは何年も前のことであり、\nPythonが言語として成熟するのにあわせて、このプロトコルも成長してきた。\n現在は4つの異なるバージョンのPickleプロトコルが存在する。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "Python 1.xは、2つのPickleプロトコルを持っていた。\n- テキスト形式(バージョン0)と\n- バイナリ形式(バージョン 1)。\n\nPython 2.3では、Pythonのクラスオブジェクトの新機能を扱うために、\n- 新しいPickleプロトコル(バージョン2)が導入された。\n\nこれはバイナリ形式。\n\nPython 3.0では、bytesオブジェクトとバイト配列の明示的なサポートをもつ、\n- もう一つのPickleプロトコルが導入された(バージョン3)。\n\nこれはバイナリ形式。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "つまり、\nPython 3はプロトコルバージョン2でPickle化されたデータを読み込むことができるが、\nPython 2はプロトコルバージョン3でPickle化されたデータを読み込むことができない。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "## Pickleファイルをデバッグ" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "Pickleプロトコルはどのような姿をしているのだろうか?" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "ファイルを逆アセンブルしたものだが、最も興味深い情報は最後の行に含まれている。\nというのも、この行にはファイルを保存したときに使ったPickleプロトコルのバージョンが記されているからだ。\nPickleプロトコルには、バージョンを示すための明示的なマーカが存在しない。\nしたがって、ファイルの保存にどのバージョンのプロトコルが用いられたのかを知るためには、Pickle化されたデータの中にある目印(“opcodes”)に目を向けて、\n「どのopcodeがどのバージョンのPickleプロトコルで導入されたのか」というハードコードされた知識をもとに推測を行うしかない。\n\n`pickletools.dis()`関数もこの方法でバージョンを識別しており、\nその結果を逆アセンブリ出力の最後の行に表示してくれる。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "import pickletools\n\nwith open('entry.pickle', 'rb') as f:\n pickletools.dis(f)", | |
"execution_count": 18, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": " 0: \\x80 PROTO 3\n 2: } EMPTY_DICT\n 3: q BINPUT 0\n 5: ( MARK\n 6: X BINUNICODE 'article_link'\n 23: q BINPUT 1\n 25: X BINUNICODE 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition'\n 104: q BINPUT 2\n 106: X BINUNICODE 'published_date'\n 125: q BINPUT 3\n 127: c GLOBAL 'time struct_time'\n 145: q BINPUT 4\n 147: ( MARK\n 148: M BININT2 2009\n 151: K BININT1 3\n 153: K BININT1 27\n 155: K BININT1 22\n 157: K BININT1 20\n 159: K BININT1 42\n 161: K BININT1 4\n 163: K BININT1 86\n 165: J BININT -1\n 170: t TUPLE (MARK at 147)\n 171: q BINPUT 5\n 173: } EMPTY_DICT\n 174: q BINPUT 6\n 176: \\x86 TUPLE2\n 177: q BINPUT 7\n 179: R REDUCE\n 180: q BINPUT 8\n 182: X BINUNICODE 'title'\n 192: q BINPUT 9\n 194: X BINUNICODE 'Dive into history, 2009 edition'\n 230: q BINPUT 10\n 232: X BINUNICODE 'internal_id'\n 248: q BINPUT 11\n 250: C SHORT_BINBYTES b'\\xde\\xd5\\xb4\\xf8'\n 256: q BINPUT 12\n 258: X BINUNICODE 'comments_link'\n 276: q BINPUT 13\n 278: N NONE\n 279: X BINUNICODE 'tags'\n 288: q BINPUT 14\n 290: X BINUNICODE 'diveintopython'\n 309: q BINPUT 15\n 311: X BINUNICODE 'docbook'\n 323: q BINPUT 16\n 325: X BINUNICODE 'html'\n 334: q BINPUT 17\n 336: \\x87 TUPLE3\n 337: q BINPUT 18\n 339: X BINUNICODE 'published'\n 353: q BINPUT 19\n 355: \\x88 NEWTRUE\n 356: u SETITEMS (MARK at 5)\n 357: . STOP\nhighest protocol among opcodes = 3\n" | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "次に示すのは、何も表示することなく、単にバージョン番号のみを返す関数だ" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "import pickletools\n\ndef protocol_version(file_object):\n maxproto = -1\n for opcode, arg, pos in pickletools.genops(file_object):\n maxproto = max(maxproto, opcode.proto)\n return maxproto\n", | |
"execution_count": 19, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "with open('entry.pickle', 'rb') as f:\n v = protocol_version(f)\n print(v)", | |
"execution_count": 20, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": "3\n" | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "## Pythonオブジェクトを他の言語で読むためにシリアライズする" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`pickle`モジュールが使っているデータ形式はPython固有のものであり、他のプログラミング言語との互換性をとる努力は行われていない。\n言語間の互換性が必要な場合は、他のシリアライズ形式に目を向ける必要がある。\n\nそのような形式の1つとして`json` がある。\n“json” は “JavaScript Object Notation” の略だが、\nこの名前に騙されてはいけない。jsonは、様々な言語間で使えるように明確に設計されている。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "Python 3の標準ライブラリの中には `json`モジュールが含まれている。\n`json`モジュールは、`pickle`モジュールと同様に、\n- データ構造をシリアライズする関数、\n- シリアライズされたデータをディスクに保存する関数、\n- シリアライズされたデータをディスクから読み込む関数、\n- データをデシリアライズして新しいPythonオブジェクトに戻す関数\n\nを持っている。\n\nしかし、重要な違いもいくつか存在する。\n- jsonのデータ形式はバイナリ形式ではなくテキスト形式だ。\n[RFC 4627](http://pentan.info/doc/rfc/j4267.html)は、jsonの形式と、個々の異なる型がどのようにテキストとしてエンコードされるのかを定義している。\n例えばブール値は、5文字の文字列'false'、もしくは4文字の文字列'true'として保存される。\njsonのすべての値は、大文字と小文字が区別される。\n\n- テキストベースの形式ではいつも問題となることだが、ホワイトスペースの扱いがある。jsonでは、値のあいだに任意個のホワイトスペース(空白・タブ・キャリッジリターン・ラインフィード) を置くことが許されている。\nこのホワイトスペースは「意味を持たない」とされている。\nすなわち、jsonのエンコーダは好きなようにホワイトスペースを加えることができるし、jsonのデコーダはホワイトスペースを無視するよう仕様で定められているのだ。これによって、jsonのデータを “pretty-print”、つまり、異なるレベルのインデントを加えて、値を値の中にきれいにネストさせて出力できるので、\n標準的なブラウザやテキストエディタで容易にこのデータを読むことができる。\nPythonのjsonモジュールは、エンコード時に `“pretty-print”` を行うオプションを持っている。\n\n- これまで何度も見てきた文字コードの問題がある。\n`json` はプレーンテキストとして値を保存するが、知っての通り「プレーンテキスト」などというものは存在しない。\n`json` は、Unicodeエンコーディング(UTF-32・UTF-16・utf-8。デフォルトはutf-8)で保存されなければならない。\nRFC 4627の第3節は、どのエンコーディングが使われているのかを見分ける方法を定義している。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "## データをjsonファイルに保存" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`json` は、JavaScriptで手作業で定義するようなデータ構造と非常によく似ている。\nこれは実際に、jsonでシリアライズされたデータをJavaScriptのeval()関数を使って「デコード」することができる。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "新しいデータ構造を定義する。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "basic_entry = {}", | |
"execution_count": 21, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "basic_entry['id'] = 256\nbasic_entry['title'] = 'Dive into history, 2009 edition'\nbasic_entry['tags'] = ('diveintopython', 'docbook', 'html')\nbasic_entry['published'] = True\nbasic_entry['comments_link'] = None", | |
"execution_count": 22, | |
"outputs": [] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "jsonはテキストベースの形式だ。つまり、このファイルはテキスト形式で開かなければならず、文字コードも指定する必要がある。utf-8を使っておけば問題は決して起きない。\n\n`pickle`モジュールと同様に、jsonモジュールは、Pythonのデータ構造と書き込み可能なストリームオブジェクトを受け取る `dump()`関数を定義している。`dump()`関数はPythonのデータ構造をシリアライズして、\nそれをストリームオブジェクトに書き込む。\nこれをwith文の中で行うことによって、処理が終わったときにファイルが適切に閉じられることを保証している。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "import json\nwith open('basic.json', mode='w', encoding='utf-8') as f:\n json.dump(basic_entry, f)\n", | |
"execution_count": 23, | |
"outputs": [] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "jsonの中身を表示する。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "with open('basic.json', mode='r', encoding='utf-8') as f:\n print(f.read())", | |
"execution_count": 24, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": "{\"id\": 256, \"published\": true, \"comments_link\": null, \"tags\": [\"diveintopython\", \"docbook\", \"html\"], \"title\": \"Dive into history, 2009 edition\"}\n" | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "jsonは、値のあいだに任意個のホワイトスペースを入れることができるので、\njsonモジュールはこの長所を生かして、さらに読みやすいjsonファイルを手軽に作る方法を提供している。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "indentパラメータを`json.dump()`関数に渡すと、\njsonファイルをより読みやすい形に整形して出力してくれる(その代わりに、ファイルサイズは増える)。\n\n`indent`パラメータは整数になる。\nこれを0にすると「各々の値ごとに1行を使う」という意味になる。\n0より大きい数を与えた場合は、「各々の値ごとに1行を使い、さらに指定した数の空白を使ってデータ構造をインデントする」という意味になる。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "with open('basic-pretty.json', mode='w', encoding='utf-8') as f:\n json.dump(basic_entry, f, indent=2) ", | |
"execution_count": 25, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "with open('basic-pretty.json', mode='r', encoding='utf-8') as f:\n print(f.read()) ", | |
"execution_count": 26, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": "{\n \"id\": 256,\n \"published\": true,\n \"comments_link\": null,\n \"tags\": [\n \"diveintopython\",\n \"docbook\",\n \"html\"\n ],\n \"title\": \"Dive into history, 2009 edition\"\n}\n" | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "## Pythonのデータ型をjsonにマッピングする" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`json` はPython固有の形式ではないので、Pythonのデータ型とのミスマッチがいくつか存在する。\nいくつかは単に名前が違うだけであるが、Pythonの重要なデータ型のうちの2つは完全に欠けてしまっている。\n\n| JSON | Python3 |\n|:--:|:--:|\n| オブジェクト|\t辞書 |\n| 配列\t| リスト |\n| 文字列 | 文字列 |\n| 整数\t| 整数 |\n| 実数\t| 浮動小数点数 |\n|\ttrue |\tTrue |\n|\tfalse |\tFalse |\n|null |\tNone |\n\nタプルとバイト列は対応していない。\njsonは配列型を持っており、jsonモジュールはこれをPythonのリストにマッピングする。\nしかし、jsonは「凍結された配列」(タプル)を持たない。\n\nまた、jsonは、文字列を非常に上手くサポートする一方で、bytesオブジェクトやバイト配列はサポートしない。\n" | |
}, | |
{ | |
"metadata": { | |
"collapsed": true | |
}, | |
"cell_type": "markdown", | |
"source": "## jsonがサポートしていないデータ型をシリアライズ" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`json` が組み込みでバイト列をサポートしていないと言えども、\nbytesオブジェクトのシリアライズが不可能なわけではない。\n\njsonモジュールは、jsonで定義されていないデータ型をエンコード/デコードするためのフックを仕掛けることができるようになっている。\n\njsonモジュールはもちろんバイト列の存在を認識しているが、\nこのモジュールはjsonの仕様によって制約されている。\n\nバイト列やjsonがネイティブでサポートしない他のデータ型をエンコードしたい場合には、それらの型のための自作のエンコーダとデコーダを用意する必要がある。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "entryデータ構造は、あらゆるものが含まれる: \n- ブール値\n- None\n- 文字列\n- 文字列のタプル\n- bytesオブジェクト\n- timeデータ構造" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "entry", | |
"execution_count": 27, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "{'article_link': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition',\n 'comments_link': None,\n 'internal_id': b'\\xde\\xd5\\xb4\\xf8',\n 'published': True,\n 'published_date': time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1),\n 'tags': ('diveintopython', 'docbook', 'html'),\n 'title': 'Dive into history, 2009 edition'}" | |
}, | |
"execution_count": 27 | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "jsonはテキスト形式。jsonファイルは、文字コードをutf-8にしてテキストモードで開く。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "import json\nwith open('entry.json', 'w', encoding='utf-8') as f:\n json.dump(entry, f)\n", | |
"execution_count": 28, | |
"outputs": [ | |
{ | |
"output_type": "error", | |
"evalue": "b'\\xde\\xd5\\xb4\\xf8' is not JSON serializable", | |
"ename": "TypeError", | |
"traceback": [ | |
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", | |
"\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", | |
"\u001b[1;32m<ipython-input-28-cf1b8398c33c>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mjson\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mwith\u001b[0m \u001b[0mopen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'entry.json'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'w'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mencoding\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m'utf-8'\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0mjson\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdump\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mentry\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", | |
"\u001b[1;32mC:\\Miniconda3\\lib\\json\\__init__.py\u001b[0m in \u001b[0;36mdump\u001b[1;34m(obj, fp, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)\u001b[0m\n\u001b[0;32m 176\u001b[0m \u001b[1;31m# could accelerate with writelines in some versions of Python, at\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 177\u001b[0m \u001b[1;31m# a debuggability cost\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 178\u001b[1;33m \u001b[1;32mfor\u001b[0m \u001b[0mchunk\u001b[0m \u001b[1;32min\u001b[0m \u001b[0miterable\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 179\u001b[0m \u001b[0mfp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwrite\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mchunk\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 180\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", | |
"\u001b[1;32mC:\\Miniconda3\\lib\\json\\encoder.py\u001b[0m in \u001b[0;36m_iterencode\u001b[1;34m(o, _current_indent_level)\u001b[0m\n\u001b[0;32m 427\u001b[0m \u001b[1;32myield\u001b[0m \u001b[1;32mfrom\u001b[0m \u001b[0m_iterencode_list\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mo\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0m_current_indent_level\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 428\u001b[0m \u001b[1;32melif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mo\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdict\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 429\u001b[1;33m \u001b[1;32myield\u001b[0m \u001b[1;32mfrom\u001b[0m \u001b[0m_iterencode_dict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mo\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0m_current_indent_level\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 430\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 431\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mmarkers\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", | |
"\u001b[1;32mC:\\Miniconda3\\lib\\json\\encoder.py\u001b[0m in \u001b[0;36m_iterencode_dict\u001b[1;34m(dct, _current_indent_level)\u001b[0m\n\u001b[0;32m 401\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 402\u001b[0m \u001b[0mchunks\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0m_iterencode\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0m_current_indent_level\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 403\u001b[1;33m \u001b[1;32myield\u001b[0m \u001b[1;32mfrom\u001b[0m \u001b[0mchunks\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 404\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mnewline_indent\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 405\u001b[0m \u001b[0m_current_indent_level\u001b[0m \u001b[1;33m-=\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", | |
"\u001b[1;32mC:\\Miniconda3\\lib\\json\\encoder.py\u001b[0m in \u001b[0;36m_iterencode\u001b[1;34m(o, _current_indent_level)\u001b[0m\n\u001b[0;32m 434\u001b[0m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Circular reference detected\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 435\u001b[0m \u001b[0mmarkers\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mmarkerid\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mo\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 436\u001b[1;33m \u001b[0mo\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0m_default\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mo\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 437\u001b[0m \u001b[1;32myield\u001b[0m \u001b[1;32mfrom\u001b[0m \u001b[0m_iterencode\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mo\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0m_current_indent_level\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 438\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mmarkers\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", | |
"\u001b[1;32mC:\\Miniconda3\\lib\\json\\encoder.py\u001b[0m in \u001b[0;36mdefault\u001b[1;34m(self, o)\u001b[0m\n\u001b[0;32m 178\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 179\u001b[0m \"\"\"\n\u001b[1;32m--> 180\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mrepr\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mo\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m+\u001b[0m \u001b[1;34m\" is not JSON serializable\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 181\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 182\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mencode\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mo\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", | |
"\u001b[1;31mTypeError\u001b[0m: b'\\xde\\xd5\\xb4\\xf8' is not JSON serializable" | |
] | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`json.dump()`関数が bytesオブジェクト`b'\\xDE\\xD5\\xB4\\xF8'` をシリアライズしようとしたが、jsonはbytesオブジェクトをサポートしていないので失敗した。\n\nどうしてもバイト列を保存したい場合には、独自の「小さなシリアライズ形式」を定義することができる。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "jsonがネイティブにサポートしないデータ型のための独自の「小さなシリアライズ形式」を定義するには、パラメータとして1つのPythonオブジェクトを受け取る関数を定義すればいい。\n\n`json.dump()`関数が自分自身ではシリアライズできない実際のオブジェクトが、\nこのPythonオブジェクトとして渡されることになる。\nこの例では、それはbytesオブジェクトの `b'\\xDE\\xD5\\xB4\\xF8'` になる。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "自作のシリアライズ関数は、`json.dump()`関数が渡してきたPythonオブジェクトの型をチェックすべき。\n厳密には、その関数が1つのデータ型だけをシリアライズするのであれば必要ないのだが、\nこのチェックは関数がどの型を対象にしているのかを非常に明白にしてくれるし、\nあとになって他のデータ型をシリアライズする必要に迫られたときの拡張も容易になる。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "今回はbytesオブジェクトを辞書に変換することにした。\n`__class__`キーが元のデータ型を(文字列'bytesとして)格納し、\n`__value__`キーが実際の値を格納する。\n\nもちろん、この値としてbytesオブジェクトを使うことはできないので、これをjsonでシリアライズできる何らかの値に変換することが必要。\n\n`bytes` は整数のシーケンスなので、各々の整数は 0–255 の範囲のいずれかになる。\n`list()`関数を使ってbytesオブジェクトを整数のリストに変換することができる。\nつまり、`b'\\xDE\\xD5\\xB4\\xF8'` は `[222, 213, 180, 248]` になる(計算すれば分かる \n16進数で\\xDEのバイトは10進数では222(16\\*13+14)。\\xD5は213。以下同様)。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "def to_json(python_object):\n if isinstance(python_object, bytes):\n return {'__class__': 'bytes',\n '__value__': list(python_object)}\n raise TypeError(repr(python_object) + ' is not JSON serializable')\n", | |
"execution_count": 29, | |
"outputs": [] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "最後の行は、シリアライズしようとしているデータは、\n組み込みのjsonシリアライザでも、自作のシリアライザでも扱えない型を含んでいるかもしれない。\nその場合は、自作のシリアライザが `TypeError` を発生させることによって、\n自作のシリアライザがその型を認識できないことを `json.dump()`関数に知らせなければならない。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "これで完了。他のことはしなくていい。\n\n具体的に言うと、この自作のシリアライズ関数は、文字列を返さずに、Pythonの辞書を返している。\n自分自身ではjsonへの完全なシリアライズを行っていない。\nやっているのはサポートされているデータ型に変換する部分だけだ。\n残りの処理は `json.dump()`関数がやってくれる。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "自作の変換用関数を `json.dump()`関数にフックするために、\nその関数を `json.dump()`関数のdefaultパラメータで渡している。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "with open('entry.json', 'w', encoding='utf-8') as f:\n json.dump(entry, f, default=to_json) \n", | |
"execution_count": 30, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "with open('entry.json', 'r', encoding='utf-8') as f:\n print(f.read())", | |
"execution_count": 31, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": "{\"article_link\": \"http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition\", \"published_date\": [2009, 3, 27, 22, 20, 42, 4, 86, -1], \"title\": \"Dive into history, 2009 edition\", \"internal_id\": {\"__value__\": [222, 213, 180, 248], \"__class__\": \"bytes\"}, \"comments_link\": null, \"tags\": [\"diveintopython\", \"docbook\", \"html\"], \"published\": true}\n" | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`time.struct_time`オブジェクトが、\n配列になってしまう問題がある。\n\n```\n\"published_date\": [2009, 3, 27, 22, 20, 42, 4, 86, -1]\n```" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "先ほどの `to_json()`関数にコードを加え、(`json.dump()`関数が扱い困っている)\nPythonオブジェクトが `time.struct_time` であるかどうかをチェックする必要がある。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "もしそうであれば、bytesオブジェクトに対して行った変換と似たようなことを行う:\n\n`time.struct_time`オブジェクトを、jsonがシリアライズできる値だけで構成された辞書に変換する。\nこの場合、日時をjsonがシリアライズできる値に最も簡単に変換するには、\n`time.asctime()`関数を使って文字列に変換すると良い。\n`time.asctime()`関数は、\n見た目の悪い`time.struct_time` を `'Fri Mar 27 22:20:42 2009'` という文字列に変換する。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "import time\n\ndef to_json1(python_object):\n if isinstance(python_object, time.struct_time):\n return {'__class__': 'time.asctime',\n '__value__': time.asctime(python_object)}\n if isinstance(python_object, bytes):\n return {'__class__': 'bytes',\n '__value__': list(python_object)}\n raise TypeError(repr(python_object) + ' is not JSON serializable')\n", | |
"execution_count": 32, | |
"outputs": [] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "これら2つのカスタムな変換をすれば、\nこれ以上の問題を起こすことなくentryデータ構造の全体がjsonにシリアライズされるはず。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "with open('entry.json', 'w', encoding='utf-8') as f:\n json.dump(entry, f, default=to_json1)", | |
"execution_count": 33, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "with open('entry.json', 'r', encoding='utf-8') as f:\n print(f.read())", | |
"execution_count": 34, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": "{\"article_link\": \"http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition\", \"published_date\": [2009, 3, 27, 22, 20, 42, 4, 86, -1], \"title\": \"Dive into history, 2009 edition\", \"internal_id\": {\"__value__\": [222, 213, 180, 248], \"__class__\": \"bytes\"}, \"comments_link\": null, \"tags\": [\"diveintopython\", \"docbook\", \"html\"], \"published\": true}\n" | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "### JSONEncoderクラスを継承する場合" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`json.dump()` の際にデフォで使われる`JSONEncoder`クラスをカスタマイズすることもできる。\n\n`default`メソッドをオーバーライドすることで、変換用関数を作れる。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "import json\nimport time\n\nclass TimeEncoder(json.JSONEncoder):\n def default(self, python_object):\n print(str(type(python_object)))\n if isinstance(python_object, time.struct_time):\n return {'__class__': 'time.asctime', '__value__': time.asctime(python_object)}\n \n if isinstance(python_object, bytes):\n return {'__class__': 'bytes', '__value__': list(python_object)}\n \n # Let the base class default method raise the TypeError\n return json.JSONEncoder.default(self, python_object)\n \n", | |
"execution_count": 35, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "with open('entry.json', 'w', encoding='utf-8') as f:\n json.dump(entry, f, cls=TimeEncoder)", | |
"execution_count": 36, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": "<class 'bytes'>\n" | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "with open('entry.json', 'r', encoding='utf-8') as f:\n print(f.read())", | |
"execution_count": 37, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": "{\"article_link\": \"http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition\", \"published_date\": [2009, 3, 27, 22, 20, 42, 4, 86, -1], \"title\": \"Dive into history, 2009 edition\", \"internal_id\": {\"__value__\": [222, 213, 180, 248], \"__class__\": \"bytes\"}, \"comments_link\": null, \"tags\": [\"diveintopython\", \"docbook\", \"html\"], \"published\": true}\n" | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "isinstance(entry['published_date'], time.struct_time)", | |
"execution_count": 38, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "True" | |
}, | |
"execution_count": 38 | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "type(entry['published_date'])", | |
"execution_count": 39, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "time.struct_time" | |
}, | |
"execution_count": 39 | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "どうやら`time.struct_time` は、独自オブジェクトでなく、namedtuple(名前付きタプル)になっているようで、\nそのため、`json.dump()` すると **タプル同様、配列** に変換されてしまっている。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "import json\n\njson = json.dumps(time.strptime('Fri Mar 27 22:20:42 2009'))\njson", | |
"execution_count": 40, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "'[2009, 3, 27, 22, 20, 42, 4, 86, -1]'" | |
}, | |
"execution_count": 40 | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "time.strptime('Fri Mar 27 22:20:42 2009')", | |
"execution_count": 41, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1)" | |
}, | |
"execution_count": 41 | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "time.strptime('Fri Mar 27 22:20:42 2009')\ntype(time.strptime('Fri Mar 27 22:20:42 2009'))", | |
"execution_count": 42, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "time.struct_time" | |
}, | |
"execution_count": 42 | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "### datetime への変換" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`time.struct_time` が使いずらいので、`datetime` を使ってみる。\n\n`time.strptime`の利点としては、文字列のフォーマットを指定しなくても`time.struct_time` に変換してくれる。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "import datetime\nst = time.strptime('Fri Mar 27 22:20:42 2009')\ndt = datetime.datetime(st.tm_year,st.tm_mon,st.tm_mday,st.tm_hour,st.tm_min,st\n.tm_sec)\ndt", | |
"execution_count": 43, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "datetime.datetime(2009, 3, 27, 22, 20, 42)" | |
}, | |
"execution_count": 43 | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`datetime.strftime()` と `datetime.strptime()` のフォーマットについては以下のリンク。\n\nhttp://docs.python.jp/3.5/library/datetime.html#strftime-strptime-behavior" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "str = dt.strftime('%Y/%m/%d')\nstr", | |
"execution_count": 44, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "'2009/03/27'" | |
}, | |
"execution_count": 44 | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false, | |
"scrolled": true | |
}, | |
"cell_type": "code", | |
"source": "str = dt.strftime('%c')\nstr", | |
"execution_count": 45, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "'Fri Mar 27 22:20:42 2009'" | |
}, | |
"execution_count": 45 | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "import time\nimport datetime\n\nentry = {}\nentry['title'] = 'Dive into history, 2009 edition'\nentry['article_link'] = 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition'\nentry['comments_link'] = None\nentry['internal_id'] = b'\\xDE\\xD5\\xB4\\xF8'\nentry['tags'] = ('diveintopython', 'docbook', 'html')\nentry['published'] = True\ntime_struct = time.strptime('Fri Mar 27 22:20:42 2009')\nentry['published_date'] = datetime.datetime(*time_struct[:6])\nentry['published_date']", | |
"execution_count": 46, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "datetime.datetime(2009, 3, 27, 22, 20, 42)" | |
}, | |
"execution_count": 46 | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "import datetime\ndef to_json(python_object):\n if isinstance(python_object, datetime.datetime):\n return {'__class__': 'datetime.datetime', '__value__': python_object.strftime('%c')}\n \n if isinstance(python_object, bytes):\n return {'__class__': 'bytes', '__value__': list(python_object)}\n \n # Let the base class default method raise the TypeError\n return json.JSONEncoder.default(self, python_object)", | |
"execution_count": 47, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "import json\nwith open('entry.json', 'w', encoding='utf-8') as f:\n json.dump(entry, f, default=to_json)", | |
"execution_count": 48, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "with open('entry.json', 'r', encoding='utf-8') as f:\n print(f.read())", | |
"execution_count": 49, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": "{\"article_link\": \"http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition\", \"published_date\": {\"__value__\": \"Fri Mar 27 22:20:42 2009\", \"__class__\": \"datetime.datetime\"}, \"title\": \"Dive into history, 2009 edition\", \"internal_id\": {\"__value__\": [222, 213, 180, 248], \"__class__\": \"bytes\"}, \"comments_link\": null, \"tags\": [\"diveintopython\", \"docbook\", \"html\"], \"published\": true}\n" | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "## jsonファイルからデータを読み込む" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`json`モジュールは、`pickle`モジュールのように `load()`関数を持っている。\nこの関数は、ストリームオブジェクトを受け取り、そのオブジェクトからjsonでエンコードされたデータを読み込み、jsonのデータ構造を写しとった新しいPythonのオブジェクトを作成する。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "今まで使っていた `entry`データ構造を削除する。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "del entry\nentry", | |
"execution_count": 50, | |
"outputs": [ | |
{ | |
"output_type": "error", | |
"evalue": "name 'entry' is not defined", | |
"ename": "NameError", | |
"traceback": [ | |
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", | |
"\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", | |
"\u001b[1;32m<ipython-input-50-9906af9c4100>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mdel\u001b[0m \u001b[0mentry\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mentry\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", | |
"\u001b[1;31mNameError\u001b[0m: name 'entry' is not defined" | |
] | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`json.load()`関数は、 `pickle.load()`関数と同様にうまく機能する。\nこの関数は、オブジェクトを渡すと新しいPythonオブジェクトを返す。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "import json\n\nwith open('entry.json', 'r', encoding='utf-8') as f:\n entry = json.load(f)\n", | |
"execution_count": 51, | |
"outputs": [] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`json.load()`関数は、`entry.json`を正常に読み込んで、そのデータを格納する新しいPythonオブジェクトを作る。\n\n悪い知らせ: \n\nこの関数は entry データ構造を再構成しない。2つの値'internal_id'と'published_date'は辞書だ。具体的に言うと、これらは、変換関数to_json()で作ったjson互換の値を持った辞書になっている。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "entry", | |
"execution_count": 52, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "{'article_link': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition',\n 'comments_link': None,\n 'internal_id': {'__class__': 'bytes', '__value__': [222, 213, 180, 248]},\n 'published': True,\n 'published_date': {'__class__': 'datetime.datetime',\n '__value__': 'Fri Mar 27 22:20:42 2009'},\n 'tags': ['diveintopython', 'docbook', 'html'],\n 'title': 'Dive into history, 2009 edition'}" | |
}, | |
"execution_count": 52 | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "`json.load()`は、先に`json.dump()`関数に渡された変換関数については何も知らない。\n`to_json()`関数とは反対のことをする関数が必要になる。\nつまり、カスタムな変換を施されたjsonオブジェクトを受け取って、それを元のPythonのデータ型に変換する関数が必要。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "datetime.datetime.strftime関数によって返された文字列をデコードするには、time.strptime()関数('time.struct_time')を使っている。\nこの関数はフォーマットされた日時を表す文字列を受け取り(形式はカスタマイズできるが、\n標準ではtime.asctime()がデフォルトで返す文字列と同じ形式(`datetime.datetime.strptime('%c')`)をとる)`time.struct_time` を返す。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "整数のリストを `bytes`オブジェクトに変換するには、bytes()関数を使うことができる。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": true | |
}, | |
"cell_type": "code", | |
"source": "def from_json(json_object):\n if '__class__' in json_object:\n if json_object['__class__'] == 'datetime.datetime':\n return time.strptime(json_object['__value__'])\n \n if json_object['__class__'] == 'bytes':\n return bytes(json_object['__value__'])\n \n return json_object", | |
"execution_count": 53, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false | |
}, | |
"cell_type": "code", | |
"source": "with open('entry.json', 'r', encoding='utf-8') as f:\n entry = json.load(f, object_hook=from_json)", | |
"execution_count": 54, | |
"outputs": [] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "デシリアライズの処理の中に `from_json()`関数をフックするために、\nこの関数を `object_hook`パラメータとして、`json.load()`関数に渡す。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false, | |
"scrolled": true | |
}, | |
"cell_type": "code", | |
"source": "entry", | |
"execution_count": 55, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "{'article_link': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition',\n 'comments_link': None,\n 'internal_id': b'\\xde\\xd5\\xb4\\xf8',\n 'published': True,\n 'published_date': time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1),\n 'tags': ['diveintopython', 'docbook', 'html'],\n 'title': 'Dive into history, 2009 edition'}" | |
}, | |
"execution_count": 55 | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "元のentryデータ構造では、`'tags'`キーの値は3つの文字列からなるタプルだったが、\n配列に変換されてしまう。" | |
}, | |
{ | |
"metadata": { | |
"trusted": true, | |
"collapsed": false, | |
"scrolled": true | |
}, | |
"cell_type": "code", | |
"source": "entry['tags']", | |
"execution_count": 56, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"metadata": {}, | |
"data": { | |
"text/plain": "['diveintopython', 'docbook', 'html']" | |
}, | |
"execution_count": 56 | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "jsonはタプルとリストを区別することができない。\njsonにはリストに似たデータ型である配列しか存在しないので、\n**jsonモジュールはシリアライズの過程でタプルとリストの両方を黙ってjsonの配列に変換する**。\nほとんどの場合、タプルとリストの違いは無視することができるが、\n`json`モジュールを使うときには、このことを心に留めておいたほうが良い。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "## jsonモジュールを使うときのまとめ" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "- bytesオブジェクトやバイト配列はサポートしない。\n- タプルと`namedtuple` は、配列に変換されてしまうので注意。`TypeError` も出ない。\n\n| JSON | Python3 |\n|:--:|:--:|\n| オブジェクト|\t辞書 |\n| 配列\t| リスト、**タプル、namedtuple** |\n| 文字列 | 文字列 |\n| 整数\t| 整数 |\n| 実数\t| 浮動小数点数 |\n|\ttrue |\tTrue |\n|\tfalse |\tFalse |\n|null |\tNone |\n\nなので、\nPython側で、「辞書、リスト、文字列、整数、浮動小数点数、ブール、None」 のみのデータ構造であれば、\n処理を追加せずともjsonとPythonオブジェクト間で整合性がとれる。" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "## 参考リンク" | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "- [Dive Into Python3 13章 PYTHONオブジェクトをシリアライズする](http://diveintopython3-ja.rdy.jp/serializing.html)\n- [pickle — Python オブジェクトの直列化 python doc](http://docs.python.jp/3/library/pickle.html)\n- [json — JSON エンコーダおよびデコーダ python doc](http://docs.python.jp/3.5/library/json.html)\n- [strftime() と strptime() の振る舞い python doc](http://docs.python.jp/3.5/library/datetime.html#strftime-strptime-behavior)" | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"name": "python3", | |
"display_name": "Python 3", | |
"language": "python" | |
}, | |
"toc": { | |
"toc_number_sections": true, | |
"toc_window_display": false, | |
"toc_cell": true, | |
"toc_threshold": "6" | |
}, | |
"language_info": { | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"mimetype": "text/x-python", | |
"version": "3.5.1", | |
"file_extension": ".py", | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
} | |
}, | |
"gist": { | |
"id": "", | |
"data": { | |
"description": "Dive Into Python3 13章メモ(Pythonオブジェクトをシリアライズ)", | |
"public": true | |
} | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 0 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment