リファクタリング的な作業(1)
(Python学習初心者の試行錯誤・備忘録です)
半月ほど前に書いた
が一応動いたことで、ちょっとした高揚感(笑)にしばらく浸っていました。少し時間が経って、冷静になってみると、コードの中身がひどいもんなので、初心者なりに「リファクタリング」的な作業をしてみようと思います。
test.py
import csv
import sqlite3
import TkEasyGUI as eg
from myspeech_lib import myspeech
mydb = "mydb.sqlite3"
#メイン画面の作成
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 flashcard():
con = sqlite3.connect(mydb)
cur = con.cursor()
def getrow(SQLstr:str):
cur.execute(SQLstr)
return cur.fetchone()
totalcount = getrow("SELECT COUNT(*) FROM t_shengci")[0]
level0count = getrow("SELECT COUNT(*) FROM t_shengci WHERE level = 0")[0]
level1count = getrow("SELECT COUNT(*) FROM t_shengci WHERE level = 1")[0]
level2count = getrow("SELECT COUNT(*) FROM t_shengci WHERE level = 2")[0]
level3count = getrow("SELECT COUNT(*) FROM t_shengci WHERE level = 3")[0]
tempid, temphanzi, temppinyin, templevel, temptimestamp \
= getrow("SELECT id, hanzi, pinyin, level, timestamp FROM t_shengci \
ORDER BY level ASC, timestamp ASC")
resdict={'id':tempid,'hanzi':temphanzi, 'pinyin':temppinyin, 'level':templevel, \
'timestamp':temptimestamp, 'totalcount':totalcount, 'level0count':level0count, \
'level1count':level1count,'level2count':level2count,'level3count':level3count}
con.close()
return(resdict)
#コンテンツ更新
def refresh_content(tempdic):
window["disp_main"].update(tempdic["hanzi"])
window["disp_sub"].update("")
window["summary"].update("レベル0:{}個 レベル1:{}個 レベル2:{}個 レベル3:{}個 合計 {}個"\
.format(tempdic["level0count"],tempdic["level1count"],tempdic["level2count"],\
tempdic["level3count"],tempdic["totalcount"]))
#学習履歴更新
def inc_level(): #覚えていた場合 levelをプラス1
con = sqlite3.connect(mydb)
con.execute("UPDATE t_shengci SET level = {},timestamp = CURRENT_TIMESTAMP WHERE id = {} "\
.format(tempdic["level"]+1,tempdic["id"]))
con.commit()
con.close()
def zero_level(): #忘れていた場合、levelを0
con = sqlite3.connect(mydb)
con.execute("UPDATE t_shengci SET level = {},timestamp = CURRENT_TIMESTAMP WHERE id = {} "\
.format(0,tempdic["id"]))
con.commit()
con.close()
#表示作成
window = make_main()
#最初の読み込み・表示
tempdic = flashcard()
refresh_content(tempdic)
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"), ))
mydb = "mydb.sqlite3"
con =sqlite3.connect(mydb)
with open(mycsv, newline='', encoding='utf-8') as csvfile:
con = sqlite3.connect(mydb)
con.execute("DELETE FROM t_shengci")
csvreader = csv.reader(csvfile, delimiter=',')
for row in csvreader:
con.execute("INSERT INTO t_shengci(hanzi, pinyin) VALUES('{}','{}')"
.format(row[0],row[1]))
con.commit()
con.close()
tempdic = flashcard()
refresh_content(tempdic)
elif event =="btn_speak": #読み上げ
myspeech(value["-LANG-"],tempdic["hanzi"])
elif event =="btn_show_pinyin": #ピンイン表示
window["disp_sub"].update(tempdic["pinyin"])
elif event =="btn_OK": #記憶定着OKなら。Levelを一つ上げ、タイムスタンプ更新,表示データ更新
inc_level()
tempdic = flashcard()
refresh_content(tempdic)
elif event =="btn_NG": #記憶定着NGなら。Levelを0に戻し、タイムスタンプ更新,表示データ更新
zero_level()
tempdic = flashcard()
refresh_content(tempdic)
window.close()
このメインのコードと、読み上げ用のmyspeech.pyというコードがある。
実行すると
と単語暗記アプリが起動すると言うものです。
重複削除
・まず、mydb = "mydb.sqlite3" というのが複数回出てきますが後のは重複なので不要⇒削除。(変数のスコープの勘違いから複数書いていました)
with文使う
con = sqlite3.connect(mydb)
con.execute(”SQL”)
con.commit()
con.close()
の記述を、
with sqlite3.connect(mydb) as con
con.execute("SQL")
con.commit()
のような書き方に変えてcon.close()を無くす。
プレースホルダにする
ここを読むと、Python の文字列操作を使用してクエリを組み立てるのはSQLインジェクションの脆弱性につながるのでよくないそうです。
with sqlite3.connect(mydb) as con:
con.execute("UPDATE t_shengci SET level = {},timestamp = CURRENT_TIMESTAMP WHERE id = {} "\
.format(0,tempdic["id"]))
con.commit()
これを
with sqlite3.connect(mydb) as con:
con.execute("UPDATE t_shengci SET level = ?,timestamp = CURRENT_TIMESTAMP WHERE id = ? "\
,(0,tempdic["id"]))
con.commit()
あと、長くなるSQL文はトリプルクォートを使って、複数行で書いた方が見やすくなりそうです。
with sqlite3.connect(mydb) as con:
con.execute("""UPDATE t_shengci
SET level = ?,timestamp = CURRENT_TIMESTAMP
WHERE id = ? """ ,(0,tempdic["id"]))
con.commit()
もっと大きい問題がある
・・とこのあたりまで来て、こんな小手先の話ではなく、もっと大きい問題があるなという気がしました。
DB操作やら、画面制御やら、いろいろな処理を「メイン」のコードの中で書いてしまっているので、見通しが悪いのです。今はたった120行程度とはいえ、もうちょっと機能拡張したら、すぐに手に負えなくなるのは想像できます。どうやって、整理したらいいか
MVCモデルとか?
ネット上で情報を探していたら、MVCというのが一般的なようです。
(この解釈で良いのかどうかわかりませんが)要はデータベース制御のところと、画面制御のところと、アプリの肝になるロジックのところは切り分けろ、って話なのだろうと解釈しました。
途中で挫折するかもだけど、試してみます。
この記事が気に入ったらサポートをしてみませんか?