リファクタリング的な作業(2)
(Python学習初心者の試行錯誤・備忘録です)
素人が「MVCモデル」などと、知ったようなことを言うと、笑われてしまうかもしれません。ただ、基本的な考え方は、「DBの操作」と「画面の操作」と「このアプリ自体のロジック」とは分離する、と言うことだろうと解釈しています。それを意識しつつソースコードをバラしてみます。
MVC分離
メインプログラムmvctest.py の外に、自作モジュールのファイル
M: mvctest_model.py
V: mvctest_view.py
C: mvctest_control.py
を用意して、sqlite3 などのDB操作はModelに、
GUIなど画面表示はViewに、
このアプリのロジックはControlの方に、と移していく。
メインからは、この3つのmoduleだけを経由する。原則それ以外のパッケージをimportせずに(つまりメインのコードでは import sqlite3 といったことをせず)操作することを念頭に整理してみます。
Model
Modelでは、データベースとのやり取りをまとめる。改めて見直すとテーブル設計の問題が気になってきました。そもそもは、これ
当初英単語学習ツールに発展させることは考えておらず、自分自身の中文発音暗記ツールとして構想し始めました。なので、テーブルが t_shengci となっています。(中文学習で新登場の単語を示す生詞(shēng cí)より)要素名も、hanzi (漢字)とpinyin(ピンイン)だったりします。
CREATE TABLE "t_shengci" (
"id" INTEGER,
"hanzi" TEXT NOT NULL,
"pinyin" TEXT,
"level" INTEGER DEFAULT 0,
"timestamp" TEXT DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY("id" AUTOINCREMENT)
);
あとから「これ、漢字⇒英単語、ピンイン⇒訳語、に置き換えてもアプリとして成り立つよね!」と思いついて、そちらを主にしましたが、要素名まで、hanzi、pinyinそのまま。
今回、「モデル」を切り分けることを意識したところで、テーブル名も要素名も「直さなきゃ」という気になってきました。
「汎用性」のある名前ならどういうのが良いでしょう? 中文とか英文とか関係なく、「単語カード」みたいなものだとすると、単語(word) と訳(translation)でしょうか。でも「クイズ」と「答え」でもいいし・・。
まあ、「暗記カード」をイメージしているから「表:face」と「裏:back」が良いかな。
CREATE TABLE IF NOT EXISTS t_cards (
id INTEGER PRIMARY KEY,
face TEXT NOT NULL,
back TEXT,
level INTEGER DEFAULT 0,
timestamp TEXT DEFAULT CURRENT_TIMESTAMP)
これに決めました。
クラス
今回、Pythonでのクラス・オブジェクト指向初挑戦です。自分は、オブジェクト指向の考え方自体は、昔 Delphi(ObjectPascal)で使ったことはありますが、Pythonでははじめて。Youtube でPythonVtuberサプーさんの動画を見て概要把握しました。
結構、Delphiとは流儀が違うんだなぁ・・。定義していないメンバ変数を、実行時にインスタンス側で追加できちゃう(というか、イニシャライザでメンバ変数を作っているのでそれしかない。)点、面白いです。
では、カードクラスというのを作ってみます。
つい数日前知ったばかりのノウハウ「テーブルが無い時には自動作成する」というのを組み込む。あとデータベース名、テーブル名、もイニシャライズ時に変えられるようにしておく。
class Cards:
#データベースのt_cardテーブル
def __init__(self, mydb="mydb.sqlite3", table="t_card") -> None:
self.mydb = mydb
self.table =table
createsql = """ CREATE TABLE IF NOT EXISTS ? (
id INTEGER PRIMARY KEY,
face TEXT NOT NULL,
back TEXT,
level INTEGER DEFAULT 0,
timestamp TEXT DEFAULT CURRENT_TIMESTAMP)
"""
with sqlite3.connect(mydb) as con:
cur = con.cursor()
cur.execute(createsql,table)
con.commit()
と思ったらエラー。例によってCopilot先生に教えてもらいます。
なるほど、書き方を変えればできることはできるが、リスクを高める可能性があると。ならば無理せず、テーブル名は固定でいいです。
class Cards:
#データベースのt_cardテーブル
def __init__(self, mydb="mydb.sqlite3") -> None:
self.mydb = mydb
self.table ="t_cards"
createsql = """ CREATE TABLE IF NOT EXISTS t_cards (
id INTEGER PRIMARY KEY,
face TEXT NOT NULL,
back TEXT,
level INTEGER DEFAULT 0,
timestamp TEXT DEFAULT CURRENT_TIMESTAMP)
"""
with sqlite3.connect(mydb) as con:
cur = con.cursor()
cur.execute(createsql)
con.commit()
テストコードで
#テストコード
def main():
mycards1 = Cards()
mycards2 = Cards("db2.sqlite3")
if __name__ == "__main__":
main()
これをやってみたところ、無事 mydb.sqlite3 とdb2.sqlite3 が自動作成され、テーブルもできていました。
ひとまず一段落。