読者です 読者をやめる 読者になる 読者になる

静かなる名辞

pythonと読書

【python】sqlite3に任意のpythonオブジェクトを突っ込む

 データベースにpythonオブジェクトをinsertしておいて、必要になったときにselectして取り出せたら便利だよね。sqlite3のドキュメントを見ると、一応やり方自体は書いてある。
12.6. sqlite3 — SQLite データベースに対する DB-API 2.0 インタフェース — Python 3.6.0 ドキュメント
 要は、pythonのオブジェクトをデータベースに格納できる型に変換するために、適合関数というものを用意してくれと言っている。これを作るのはユーザー任せ。The Zen of Pythonの、たったひとつの冴えたやり方はどうした。
 pythonでオブジェクトのシリアライズといえば、pickleである。欠点は幾らでも思いつく(関数やクラスの定義を持ってくれない、ラムダ式やクラスメソッドが入れられない、その他)けど、これはこれで便利なものである。ただし、pickleでシリアライズしたデータはけっこう巨大なので、基本的に圧縮して突っ込んだ方がなにかと良いだろうということになる。
 仰々しく書いてきたけど、ここまでは過去に記事にした内容。今回の主旨はこれをデータベースに入れてみることである。
 まずこんな変換関数を作る。

import pickle
import bz2
PROTOCOL = pickle.HIGHEST_PROTOCOL

def ptoz(obj):
    return bz2.compress(pickle.dumps(obj, PROTOCOL), 3)

def ztop(b):
    return pickle.loads(bz2.decompress(b)) 

 これで、任意のpythonオブジェクト(ただしpickle化できるものだけ)を、bz2で圧縮されたbyte列に変換・逆変換できる。
 あとはsqlite3に入れるだけ。とりあえず、データベースとテーブルは手動で作っておこう。

$ sqlite3 test.db
sqlite> create table pyobjs(id integer primary key, name text, obj blob);

 こんなコードで動かしてみる。

# coding: UTF-8

import sqlite3
import pickle
import bz2

PROTOCOL = pickle.HIGHEST_PROTOCOL

def ptoz(obj):
    return bz2.compress(pickle.dumps(obj, PROTOCOL), 3)

def ztop(b):
    return pickle.loads(bz2.decompress(b)) 

dbname = 'test.db'

def main():
    objs = ["hoge", ("h","o","g","e"), {0:"h", 1:"o", 2:"g", 3:"e"}]
    obj_names = ["hoge_str", "hoge_tuple", "hoge_dict"]

    conn = sqlite3.connect(dbname)
    c = conn.cursor()
    
    insert_sql = "insert into pyobjs (name, obj) values (?,?)"
    insert_objs = list(zip(obj_names, [ptoz(x) for x in objs]))
    c.executemany(insert_sql, insert_objs)
    conn.commit()

    select_sql = 'select * from pyobjs'
    for row in c.execute(select_sql):
        print((row[0], row[1], ztop(row[2])))
    conn.close()


if __name__ == "__main__":
    main()

 実行結果。

(1, 'hoge_str', 'hoge')
(2, 'hoge_tuple', ('h', 'o', 'g', 'e'))
(3, 'hoge_dict', {0: 'h', 1: 'o', 2: 'g', 3: 'e'})

 だいたい納得の行く感じになっている。

 個人的にはこの程度の処理は標準でサポートしてくれと思わなくもないけど、それはpickleとかsqlite3のモジュールがやるべき仕事ではないので、ユーザー側でやってくれという思想なのかもしれない。