リファクタリング的な作業(5)
(Python学習初心者の試行錯誤・備忘録です)
前回、データベースを操作する「モデル」のコード
mvctest_model.pyが一段落したので、
で書いていたtest.pyを書き換えます。mvctest_model.pyをimport して、Cardsクラスを利用する形に置き換えます。
Cardsクラスに用意した関数は、これまでのところ
__init__()
_create_table()
loadfromcsv(csvfilename:str)
loadfromlist(datalist:list)
clear_table()
select_count_where(condition, *params)
select_topcard_where(condition, *params)
update_topcard(self, condition, *params)
この8つを組み合わせて処理を書きます
import TkEasyGUI as eg
from myspeech_lib import myspeech
from mvctest_model import Cards
def make_main():
lfont = ('標楷體', 100) #大きい表示用フォント
sfont = ('Arial', 50) #小さい表示用フォント
btfont= ('Arial', 20) #ボタン表示用フォント
#表示サンプル
myword = '仮置き'; pinyin = 'karioki'
menu_def = [
['ファイル',['読み込み',['CSV::LoadCSV'], '---', 'Exit']],
]
#メイン画面のレイアウト
layout_main =[
[eg.Menu(menu_def)],
[eg.Combo(values=["zh-TW","ja","en-US"],default_value="zh-TW",key="-LANG-")],
[eg.Button("発声", font = btfont, key ="btn_speak"),
eg.Button("連想", font = btfont, key ="btn_show_pinyin"),
eg.Button("覚えた", font = btfont, key ="btn_OK"),
eg.Button("まだ", font = btfont, key ="btn_NG")],
[eg.Text('サマリー仮置き',
key="summary",font=('Arial', 15))],
[eg.Text(myword, font = lfont, key="disp_main")],
[eg.Text(pinyin, font = sfont, key="disp_sub")],
]
return eg.Window("単語暗記 for Windows(Python版)", layout=layout_main, finalize=True)
#コンテンツ更新
def refresh_content(mycards:Cards):
if mycards.topcard is not None:
window["disp_main"].update(mycards.topcard['face'])
window["disp_sub"].update("")
window["summary"].update("レベル0:{}個 レベル1:{}個 レベル2:{}個 レベル3:{}個 合計 {}個"\
.format(
mycards.select_count_where("level = 0"),
mycards.select_count_where("level = 1"),
mycards.select_count_where("level = 2"),
mycards.select_count_where("level = 3"),
mycards.select_count_where("level >= 0")))
#表示作成
window = make_main()
#最初の読み込み・表示
mycards = Cards()
mycards.select_topcard_where("level >= 0 ORDER BY level ASC, timestamp ASC ")
refresh_content(mycards)
while True:
event, value = window.read()
if event == eg.WIN_CLOSED or event == "Exit": #終わるとき
break
elif event == "LoadCSV": #学習教材をCSVで読み込む処理、古いのは削除
mycsv = eg.popup_get_file("CSVファイルを選択",
file_types=(("All Files", "*.*"), ("CSV Files", "*.csv"), ))
mycards.clear_table()
mycards.loadfromcsv(mycsv)
mycards.select_topcard_where("level >= 0 ORDER BY level ASC, timestamp ASC ")
elif event =="btn_speak": #読み上げ
myspeech(value["-LANG-"],mycards.topcard["face"])
elif event =="btn_show_pinyin": #ピンイン表示
window["disp_sub"].update(mycards.topcard["back"])
elif event =="btn_OK": #記憶定着OKなら。Levelを一つ上げ、タイムスタンプ更新,表示データ更新
if mycards.topcard['level'] <= 3:
mycards.update_topcard("level = ?, timestamp = CURRENT_TIMESTAMP ",(mycards.topcard['level']+1,)) #レベルインクリメント
mycards.select_topcard_where("level >= 0 ORDER BY level ASC, timestamp ASC ")
refresh_content(mycards)
elif event =="btn_NG": #記憶定着NGなら。Levelを0に戻し、タイムスタンプ更新,表示データ更新
mycards.update_topcard("level = ?, timestamp = CURRENT_TIMESTAMP ",(0,)) #レベルzero
mycards.select_topcard_where("level >= 0 ORDER BY level ASC, timestamp ASC ")
refresh_content(mycards)
window.close()
120行程度から⇒80行程度?少しだけスッキリしました。
動作も問題ないことを確認しました。
ControllerとViewを分けたい
(とくにこの辺、概念や用語の使い方など、いろいろ間違っているかもしれません。初心者が、こう考えた、という備忘録ですので・・。)
この段階で気になるのは、while True:のループの中でイベント処理するところでmycards というCardsのインスタンスが直接出てきごちゃごちゃ書いて いることですが、これはよろしくないですね。
画面の設計もmakemain()関数とか、メインのコードにあると、ごちゃごちゃしてしまう。
ということで切り分けにチャレンジしてみます。
View
メインで、GUIを操作している部分をView_EGというクラスにまとめてみます。make_main() という関数をほぼそのままクラスにしただけですがこれを mvctest_view.py という別のファイルに切り分けました。Viewの中にウィンドウ変数を抱え込むようにします。この機に、各Widgetの key名もつけなおしました。
mvctest_view.py
import TkEasyGUI as eg
class View_EG:
def __init__(self) -> None:
lfont = ('標楷體', 100) #大きい表示用フォント
sfont = ('Arial', 50) #小さい表示用フォント
btfont= ('Arial', 20) #ボタン表示用フォント
#表示サンプル
myword = '仮置き'; pinyin = 'karioki'
menu_def = [
['ファイル',['読み込み',['CSV::LoadCSV'], '---', 'Exit']],
]
layout = [
[eg.Menu(menu_def)],
[eg.Combo(values=["zh-TW","ja","en-US"],default_value="zh-TW",key="-LANG-")],
[eg.Button("発声", font = btfont, key ="-BTN_SPEAK-"),
eg.Button("連想", font = btfont, key ="-BTN_SHOW_BACK-"),
eg.Button("覚えた", font = btfont, key ="-BTN_MEMORIZE-"),
eg.Button("まだ", font = btfont, key ="-BTN_FORGET-")],
[eg.Text('サマリー仮置き',
key="-DISP_SUMMARY-",font=('Arial', 15))],
[eg.Text(myword, font = lfont, key="-DISP_MAIN-")],
[eg.Text(pinyin, font = sfont, key="-DISP_SUB-")],
]
self.window = eg.Window("単語暗記 for Windows(Python版)", layout=layout, finalize=True)
Controller
「コントローラー」のクラスを作って、「処理」のロジックをその中に移し、これも別ファイルに切り分ける。
mvctest_controller.py
from mvctest_model import Cards
class Controller:
def __init__(self) -> None:
self.mycards = Cards()
def loadcsv(self, csvfilename:str) -> None:
self.mycards.clear_table()
self.mycards.loadfromcsv(csvfilename)
def pickupcard(self, lowerlevel=0) -> None:
self.mycards.select_topcard_where("level >= ? \
ORDER BY level ASC, timestamp ASC ",(lowerlevel,))
def inc_level(self) -> None:
if self.mycards.topcard['level'] < 3:
self.mycards.update_topcard("level = ?, timestamp = CURRENT_TIMESTAMP "\
,(self.mycards.topcard['level']+1,)) #レベルインクリメント
self.mycards.select_topcard_where("level >= 0 ORDER BY level ASC, timestamp ASC ")
def zero_level(self)-> None:
self.mycards.update_topcard("level = ?, timestamp = CURRENT_TIMESTAMP ",(0,)) #レベルzero
self.mycards.select_topcard_where("level >= 0 ORDER BY level ASC, timestamp ASC ")
def refresh_view(self,window):
if self.mycards.topcard is not None:
window["-DISP_MAIN-"].update(self.mycards.topcard['face'])
window["-DISP_SUB-"].update("")
window["-DISP_SUMMARY-"].update("レベル0:{}個 レベル1:{}個 レベル2:{}個 レベル3:{}個 合計 {}個"\
.format(
self.mycards.select_count_where("level = 0"),
self.mycards.select_count_where("level = 1"),
self.mycards.select_count_where("level = 2"),
self.mycards.select_count_where("level = 3"),
self.mycards.select_count_where("level >= 0")))
Main
そして、メインのコードは
from mvctest_view import View_EG,eg
from mvctest_controller import Controller
from myspeech_lib import myspeech
#表示作成
myview = View_EG()
#最初の読み込み・表示
mycontroller = Controller()
mycontroller.pickupcard()
mycontroller.refresh_view(myview.window)
while True:
event, value = myview.window.read()
if event == eg.WIN_CLOSED or event == "Exit": #終わるとき
break
elif event == "LoadCSV": #学習教材をCSVで読み込む処理、古いのは削除
mycsv = eg.popup_get_file("CSVファイルを選択",
file_types=(("All Files", "*.*"), ("CSV Files", "*.csv"), ))
mycontroller.loadcsv(mycsv)
mycontroller.pickupcard(0)
mycontroller.refresh_view(myview.window)
elif event =="-BTN_SPEAK-": #読み上げ
myspeech(value["-LANG-"],mycontroller.mycards.topcard["face"])
elif event =="-BTN_SHOW_BACK-": #裏面表示
myview.window["-DISP_SUB-"].update(mycontroller.mycards.topcard["back"])
elif event =="-BTN_MEMORIZE-": #記憶定着OKなら。Levelを一つ上げ、タイムスタンプ更新,表示データ更新
mycontroller.inc_level()
mycontroller.pickupcard(0)
mycontroller.refresh_view(myview.window)
elif event =="-BTN_FORGET-": #記憶定着NGなら。Levelを0に戻し、タイムスタンプ更新,表示データ更新
mycontroller.zero_level()
mycontroller.pickupcard(0)
mycontroller.refresh_view(myview.window)
myview.window.close()
40行程度まで短くなりました。
当初、while True のループもコントローラーの方に移そうとしたのですが、どうも、うまく書けなかったので、このループ(イベントと、コントローラーの処理とを関連付ける部分)だけメインのコードの中に残すことにしました。
以上、自己流のリファクタリング作業でした。元のコードに比べれば、分割することである程度、見通しが良くなったのではないかなと思います。