はじめに
RVは大量のデータを表示する為のwidgetです。となればそれにdatabase内のデータを映したいと考えるのは自然な事だと思います。実際にdatabaseとしてsqlite3を使ってみます。
import sqlite3
conn = sqlite3.connect(...)
cur = conn.cursor()
rv = ...
rv.data = cur.execute("SELECT * FROM 何かの表")
AttributeError: 'tuple' object has no attribute 'get'
cur.execute()
はdatabase検索結果を取り出すためのiteratorを返す1のですが、そのiteratorから取り出せるのはtupleであるため、辞書のiterableを期待しているrv.data
へは代入できず上記のように怒られます。それ故に普通はRVに渡す際に以下のような変換が生じます。
rv.data = ({'鍵1': 値1, '鍵2': 値2,} for 値1, 値2 in cur.execute("SELECT * FROM 何かの表"))
実際に速度を測ったわけではないのですが(←重要)このような変換が生じるのが気に入らない(?)ので、この投稿ではどうにかしてそれを無くせないか考えてみます。
sqlite3.Rowの場合
sqlite3は検索結果の行を辞書のように扱えるようにしてくれる手段が存在します。それがsqlite3.Rowです。
conn.row_factory = sqlite3.Row
cur = conn.cursor()
for row in cur.execute("SELECT * FROM 何かの表"):
print(row['列名'])
このように表の列名を鍵として値を取り出せるようになります。しかしながら実際にこれをrv.data
に代入すると
AttributeError: 'sqlite3.Row' object has no attribute 'get'
という風に怒られてしまいました。どうやらsqlite3.Row
は辞書と同じmethodを全て備えているわけではなさそうです。なので次は派生classを用意して足りないmethodを補ってあげてみます。
sqlite3.Rowを拡張した場合
最初に思い浮かぶのは次のような派生classです。
class RVCompatibleRow(sqlite3.Row):
def get(self, key, default_value):
return self[key]
conn.row_factory = RVCompatibleRow
するとどうなったかというと
(tracebackも一部載せている)
File "略/.venv/lib/python3.8/site-packages/kivy/uix/recyclelayout.py", line 154, in compute_sizes_from_data
ph = item.get('pos_hint', ph)
File "略/test_rv_sqlite3.py", line 41, in get
return self[key]
IndexError: No item with that key
という風に怒られました。実はph = item.get('pos_hint', ph)
は一番最初にAttributeError
が出たときからあったtracebackなんですが、RVはこのようにpos_hint
やsize_hint
のようなwidgetの配置に関わる情報が辞書に含まれている場合に何らかの特別な処理を行っています。なのでRVCompatibleRow
を次のように修正しました。
class RVCompatibleRow(sqlite3.Row):
def get(self, key, default_value):
return default_value if key not in self.keys() else self[key]
ただしdatabaseにwidgetの配置に関わる情報、つまりはデータの表示の仕方に関わる情報を含める状況は限られると思うので、もし自分自身で.get()
を使わないのであればこれは
class RVCompatibleRow(sqlite3.Row):
def get(self, key, default_value):
return default_value
としてしまっても良い気がします、少しでも速くするために。
次に出てきたerrorは
AttributeError: 'RVCompatibleRow' object has no attribute 'items'
です。というわけで.items()
も実装してあげましょう。
class RVCompatibleRow(sqlite3.Row):
# 実装その一 : 列名による検索が無いので速いかもしれない(実際に測ってはいない)
def items(self):
return zip(self.keys(), self)
# 実装そのニ
# def items(self):
# for key in self.keys():
# yield (key, self[key])
すると無事databaseの検索結果を映すことができました。
終わりに
いきなり上のscreenshotを見せられても意味不明ですね。databaseの中身が知りたい人はソースコードを覗いてください。とにかく今回の検証によってsqlite3の検索結果の列名と同じ名前のpropertyを持つviewclassを用意するだけで検索結果をRVに映せるとわかりました。
話は逸れますがSQLiteはC言語で書かれているのでPythonプログラムがCの計算能力を得られるというのは勿論なんですが
- コード中にSQLが現れることでdatabaseを触っている事が視覚的に分かりやすくなってコードが読みやすくなる
- アプリケーションの設定ファイルとして使うと自分で設計する手間が省ける
とかもかなり大きいですね。最初は嫌っていたのですが今は気に入っています。 m(_ _)m
-
cur
自体がiteratorでcur.execute()
はcur
その物を返す ↩