LoginSignup
1
0

More than 1 year has passed since last update.

RecycleView攻略 4日目 sqlite3との連携

Last updated at Posted at 2021-09-20

はじめに

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_hintsize_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 at 2021-09-20 21-57-37.png

終わりに

いきなり上のscreenshotを見せられても意味不明ですね。databaseの中身が知りたい人はソースコードを覗いてください。とにかく今回の検証によってsqlite3の検索結果の列名と同じ名前のpropertyを持つviewclassを用意するだけで検索結果をRVに映せるとわかりました。

話は逸れますがSQLiteはC言語で書かれているのでPythonプログラムがCの計算能力を得られるというのは勿論なんですが

  • コード中にSQLが現れることでdatabaseを触っている事が視覚的に分かりやすくなってコードが読みやすくなる
  • アプリケーションの設定ファイルとして使うと自分で設計する手間が省ける

とかもかなり大きいですね。最初は嫌っていたのですが今は気に入っています。 m(_ _)m


  1. cur自体がiteratorでcur.execute()curその物を返す 

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0