[Python] 241 tkinter 21 ウィンドウとフレームのclass化

[Python]240の続きです。

ウィンドウとフレームをclass化しました。

tkinterドキュメントの’A Simple Hello World Program’を参考に書き上げました。

class化については麻雀アプリでも扱っていたのでスムーズでした。

これですっきりとしたコードになりました。

import tkinter as tk
import tkinter.font as font
from tkinter import ttk

class Horse(tk.Tk):
    def __init__(self, master=None):
        super().__init__(master)
        self.title("HORSE SEARCH")
        self.geometry("310x130")
        self.configure(background = '#B0E0E6')
        self.create_menu()
        # Horseのグリッドを 1x1 にする
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

    def create_menu(self):
        menubar = tk.Menu(self)

        # 親メニューの設定
        menu_kinou = tk.Menu(menubar)
        menubar.add_cascade(label='機能', menu=menu_kinou)

        # 子メニューの設定
        menu_kinou.add_command(label='馬名検索', command=create_A)
        menu_kinou.add_separator()
        menu_kinou.add_command(label='レース検索', command=create_B)

        self.config(menu=menubar)

class FrameA(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.configure(background = '#B0E0E6')
        self.grid(row=0,column=0, sticky=tk.NSEW, padx=5, pady=10)
        self.create_widgets()

    def create_widgets(self):
        # ラベルの作成・配置
        label = tk.Label(self,text='馬名',background = '#B0E0E6',foreground = '#8b0000',font=my_font)
        label.grid(row=0, column=0)

        # パス入力エントリの作成・配置
        entry = tk.Entry(self,width=15,background = '#98fb98',foreground = '#8b0000',font=my_font)
        entry.grid(row=0,column=1)

        # 実行ボタンの作成・配置
        btn = tk.Button(self, text="検索",command=lambda:var.set(1),width=2,font=my_font)
        btn.grid(row=0,column=2,padx=2)

        # クリアボタンの作成・配置
        btn2 = tk.Button(self, text="クリア",command=lambda:entry.delete(0,tk.END),width=2,font=my_font2)
        btn2.grid(row=1,column=2,padx=2)

class FrameB(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.configure(background = '#B0E0E6')
        self.grid(row=0,column=0, sticky=tk.NSEW, padx=5, pady=10)
        self.create_widgets()

    def create_widgets(self):
        # frameB2,frameB3の作成
        frameB2 = tk.Frame(self,background = '#B0E0E6')
        frameB2.grid(row=1,column=1, sticky=tk.NSEW, padx=5, pady=0)

        frameB3 = tk.Frame(self,background = '#B0E0E6')
        frameB3.grid(row=2,column=1, sticky=tk.NSEW, padx=5, pady=0)

        # frameBラベル1の作成・配置
        labelB = tk.Label(self,text='レース',background = '#B0E0E6',foreground = '#8b0000',font=my_font)
        labelB.grid(row=0, column=0)

        # frameBラベル2の作成・配置
        labelB2 = tk.Label(self,text='開始日',background = '#B0E0E6',foreground = '#8b0000',font=my_font2)
        labelB2.grid(row=1, column=1,sticky=tk.W)

        # frameBラベル3の作成・配置
        labelB3 = tk.Label(self,text='終了日',background = '#B0E0E6',foreground = '#8b0000',font=my_font2)
        labelB3.grid(row=2, column=1,sticky=tk.W)

        # frameBエントリ1の作成・配置
        entryB = tk.Entry(self,width=15,background = '#98fb98',foreground = '#8b0000',font=my_font)
        entryB.grid(row=0,column=1)

        # frameBエントリ2の作成・配置
        entryB2 = tk.Entry(frameB2,width=8,background = '#98fb98',foreground = '#8b0000',font=my_font)
        entryB2.pack(padx=2,side=tk.RIGHT)

        # frameBエントリ3の作成・配置
        entryB3 = tk.Entry(frameB3,width=8,background = '#98fb98',foreground = '#8b0000',font=my_font)
        entryB3.pack(padx=2,side=tk.RIGHT)

        # frameB実行ボタンの作成・配置
        btnB = tk.Button(self, text="検索",command=lambda:var.set(2),width=2,font=my_font)
        btnB.grid(row=0,column=2,padx=2)

        # frameBクリアボタンの作成・配置
        btnB2 = tk.Button(self, text="クリア",command=lambda:entryB.delete(0,tk.END),width=2,font=my_font)
        btnB2.grid(row=1,column=2,padx=2)

def create_A():
    global frame

    frame.destroy
    children[-3].delete(0,tk.END)
    frame = FrameA(master=horse)

def create_B():
    global frame
    
    frame.destroy
    children[1].delete(0,tk.END)
    frame = FrameB(master=horse)

# ウィンドウの作成
horse = Horse(master=None)

# フォント設定
my_font = font.Font(horse,family="System",size=18,weight="normal")
my_font2 = font.Font(horse,family="System",size=16,weight="normal")

# フレームの作成
frame = FrameA(master=horse)

# IntVarの初期化
var = tk.IntVar()

for i in range(1000):
    print(f'var for文先頭 {var.get()}')

    children = frame.winfo_children()
    print(f'children {children}')

    if var.get() == 0 or var.get() == 1:
        print('分岐A')
        # 馬名の入力を待機
        children[2].wait_variable(var)

        # 入力した馬名を取得
        name = children[1].get()
        print(f'name {name}')
        
        # 馬名検索モジュールは省略

    else:
        print('分岐B')
        children = frame.winfo_children()
        print(f'elseウィジェットinfo\n{children}\n')

        # レース名の入力を待機
        children[-2].wait_variable(var)

        # 入力したレース名を取得
        race = children[-3].get()
        print(f'race {race}')

        # レース検索モジュールは省略

frame.mainloop()

[Python] 240 tkinter 20 画面遷移の繰り返し対応

[Python] 239の続きです。

別フレームの作成や元フレームの消去などで画面を遷移させるのは容易ですが、画面内への入力内容を取得するのにかなり苦労しました。

結局Widget変数IntVarを利用して解決しました。具体的にはIntVarが0または1であれば画面A、2であれば画面Bというif文を作成しました。

if文から抜け出すために検索ボタンを2回クリックする必要がありますが、この方法であればどう画面遷移しても固まらずに対応できます。ただし画面Aと画面Bでエントリの位置が異なることが条件になります。

ネットには日本語・英語共にピッタリな情報は見あたらず、一晩考えてようやくアイデアが浮かびました。

いつものことですが、出来上がってみると実にシンプルな内容です。

# frameBを消去・frameAを作成、frameAを消去・frameBを作成する関数を設定する
# 上記関数とメニューの'馬名検索','レース検索'をそれぞれ連携させる

<中略>

# 親メニューの設定
menu_kinou = tk.Menu(menubar)
menubar.add_cascade(label='機能', menu=menu_kinou)

# 子メニューの設定
menu_kinou.add_command(label='馬名検索', command=create_A)
menu_kinou.add_separator()
menu_kinou.add_command(label='レース検索', command=create_B)

<中略>

# 初期画面・実行ボタンの作成・配置
var = tk.IntVar()
btn = tk.Button(frame, text="検索",command=lambda:var.set(1),width=2,font=my_font)
btn.grid(row=0,column=2,padx=2)

<中略>

for i in range(1000):
    print(f'var for文先頭 {var.get()}')
    try:
        children = frame.winfo_children()
    except:
        children = frameB.winfo_children()

    if var.get() == 0 or var.get() == 1:
        print('分岐A')
        # 馬名の入力を待機
        btn.wait_variable(var)

        # 入力した馬名を取得
        try:
            name = children[1].get()
        except:
            pass
        else:
            print(f'name {name}')
            # 実際は<馬名検索モジュール>

    else:
        print(f'分岐B')
        children = frameB.winfo_children()
  
        # レース名の入力を待機
        children[-2].wait_variable(var)

        # 入力したレース名を取得
        try:
            race = children[-3].get()
        except:
            pass
        else:
            print(f'race {race}')
            # 実際は<レース検索モジュール>

horse.mainloop()
--------------------------------------------------

出力
--------------------------------------------------
var for文先頭 0
分岐A
name サリオス
var for文先頭 1
分岐A
name クロノジェネシス
var for文先頭 1
分岐A
var for文先頭 2
分岐B
race 有馬記念
var for文先頭 2
分岐B
var for文先頭 1
分岐A

[Python] 239 tkinter 19 メニューの設定と画面の切り替え

引っ越ししてからは初のtkinterネタです。

MySQLツールにメニューバーを設置し、馬名検索とレース検索で画面を切り替えるようにしました。

Macの場合はメニューバーはディスプレイ上端に表示されるので注意が必要です。

# rootの設定
root = tk.Tk()

# メニューバーの設定
menubar = tk.Menu(root)

# 親メニューの設定
menu_kinou = tk.Menu(menubar)
menubar.add_cascade(label='機能', menu=menu_kinou)

# 子メニューの設定
menu_kinou.add_command(label='馬名検索', command=lambda:frame.tkraise())
menu_kinou.add_separator()
menu_kinou.add_command(label='レース検索', command=lambda:frameB.tkraise())

root.config(menu=menubar)

<中略>

# frameBの作成・配置
frameB = tk.Frame(root,background = '#B0E0E6')
frameB.grid(row=0,column=0, sticky=tk.NSEW, padx=5, pady=10)

<中略>

# frameを前面にする
frame.tkraise()

[Python] 238 tkinter 18 MySQLツールのGUI化

MySQLツールの土台ができたので早速GUI化しました。

いつものガワを使い回しです。

今は競走馬成績検索のみですが、少しずつ拡充していきます。

次は過去レース検索あたりでしょうか。

import glob,os,shutil
import sys,json
import tkinter as tk
import tkinter.font as font
from tkinter import ttk
from HR_library import mysql_search # [Python]237のコードをモジュール化

<中略>

for i in range(100): # 100回処理可能
    # 馬名の入力を待機
    btn.wait_variable(var_act)

    # 入力した馬名を取得
    name = entry.get()

    # 競走馬成績を検索
    mysql_search.Mysql_search().horse_result(name)

root.mainloop()

[Python] DB仮完成までの道のり

昨年の今頃は主要都道府県のコロナ集計データをスクレイピングしていました。サイトの形式が自治体によってバラバラかつ頻繁にXPathが変わるのでコード管理が大変でした。今はYahoo!のトップに特集サイトがあるのでそれを利用しています。

プログラミング未経験者が2018年春にスクレイピングにいきなり挑戦して玉砕。一昨年12月にホビープログラマとなり、2020年3月から本格的にスクレイピングに取り組み始めて関係ないコードも色々書きながらここまで1年ちょっと掛かりました。感慨深いものがあります。

これまで何冊か関連本を購入しましたが積ん読状態でスクールも利用せず、ほとんど開発ドキュメントやネット情報だけでここまで来ました。

プログラミング学習を通して、自分が座学やビデオ・書籍を使った学習に向いていないことが改めて良くわかりました。

何かを知りたいと思った時にすぐに関連情報を収集し、自分なりに仮説を立て検証を繰り返すというやり方が向いているようです。

実践を通して経験値を増やし、後から体系化していく感じでしょうか。とにかく能動的に学習しないと身につかないです。

理論科学よりも実験科学が好きなのも、そういった性格によるものでしょう。

[Python] 237 MySQL 05 馬名からレース検索・CSV出力

馬名からのhorseID検索、raceID検索、レース名検索を全てMySQLで実行しました。

結果出力までたった1秒の高速処理でかなり快適です。

CSVファイルは開けたときの見栄えが今ひとつなのでHTML化も考えています。

import mysql.connector,glob,csv,re,datetime,sys

config = {
  'user': 'root',
  'password': 'root',
  'host': 'localhost',
  'port': 3306,
  'raise_on_warnings': True
}

print('検索したい馬名を入力してください')
name = input()

conn = mysql.connector.connect(**config)
cur = conn.cursor()

horseID_l = list()
for y in range(1986,2019 +1):
	table = f'horse{y}'
	try:
		cur.execute(f'SELECT horseID FROM horse_list.{table} WHERE 検索馬名 = "{name}"')
	finally:
		result = cur.fetchall()
		if result != []:
			horseID_l.append(result)

print(horseID_l)

if len(horseID_l) >= 2:
    print('該当する馬が複数います。番号を入力してください。')
    for i,data in enumerate(horseID_l):
        print(f'{i+1} {data}')
    num = input()
    id = str(horseID_l[int(num)-1][0]).replace('(','').replace(')','').replace(',','')

elif len(horseID_l) == 0:
    print('該当する馬はいません')
    sys.exit()

else:
    id = str(horseID_l[0][0]).replace('(','').replace(')','').replace(',','')

print(f'検索馬のID {id} 誕生年 {id[0:4]}')

start_year = int(id[0:4]) + 2
print(f'最短デビュー年 {start_year}')

data_l_pre = list()
for y in range(start_year,2021 +1):
	table = f'race_r_{y}'

	try:
		cur.execute(f'SELECT 着順,枠番,馬番,斤量,騎手,タイム,通過,上り,単勝,人気,馬体重,賞金,raceID FROM horse_race_result.{table} WHERE horseID = {id}')
	finally:
		result = cur.fetchall()
		# print(result)
		if result != []:
			data_l_pre.append(result)

# ネストを平滑化する
data_l = [e for l in data_l_pre for e in l]

print(f'出走数 {len(data_l)}')
print(data_l)

raceID_l = [l[-1] for l in data_l]
print(raceID_l)

data2_l_pre = list()
for id in raceID_l:
    table = f'race_n_{id[1:7]}'
    try:
        cur.execute(f'SELECT 日付,開催,レース,レース名,コース,天候,馬場状態 FROM horse_race_name.{table} WHERE raceID = "{id}"')
    finally:
        result = cur.fetchall()
        if result != []:
            data2_l_pre.append(result)

# 接続終了
cur.close()
conn.close()

# ネストを平滑化する
data2_l = [e for l in data2_l_pre for e in l]

print(f'data2_lデータ数 {len(data2_l)}')
print(data2_l)

# ソート用日付データの作成
date_l = list()
for d in data2_l:
    date = d[0]
    date_dt = datetime.datetime.strptime(date,'%Y年%m月%d日')
    date_d = datetime.date(date_dt.year, date_dt.month, date_dt.day)
    date_l.append(str(date_d))

print(f'date_lデータ数 {len(date_l)}')
print(date_l)

# レースデータ、結果データ、日付データを結合する
data3_l = list()
for a,b,c in zip(data2_l,data_l,date_l):
    d = list(a) + list(b) + [c]
    data3_l.append(d)

print(f'data3_lデータ数 {len(data3_l)}')
print(data3_l)

# 日付でソートする
data4_l = sorted(data3_l, key=lambda x: x[-1])
print(f'data4_lデータ数 {len(data4_l)}')
print(data4_l)

# 列タイトルを追加する
column_title = [['日付','開催','レース','レース名','コース','天候','馬場状態','着順','枠番','馬番','斤量','騎手','タイム','通過','上り','単勝','人気','馬体重','賞金','raceID','ソート用日付']]
data5_l = column_title + data4_l
print(f'data5_lデータ数 {len(data5_l)}')
print(data5_l)

# 結果ファイルの作成
datetime_now = datetime.datetime.now()
datetime_now_str = datetime_now.strftime('%y%m%d%H%M')
filename = f"/Desktop/{datetime_now_str}_mysql.csv"

with open(filename, 'w', newline='',encoding = 'shift_jis') as f:
    writer = csv.writer(f)
    writer.writerows(data5_l)

[Python] 236 MySQL 04 馬名からレース検索

[Python] 235の続きです。

さすがに1万ファイルは多かったようで良く見るとphpMyAdminが白旗をあげていました。

そこでレース結果ファイルを年毎に1つにまとめてインポートしました。

これでレース検索するとraceID検索までで2秒程度でした。しかし結合した年ファイルであればCSV横断検索でも同じ位です。

とは言えSQLによる値の取り出しやすさは捨てがたいものがあり、horseIDの取得からレース検索完了までフルでMySQLを使ったらどんな感じになるかか試してみたいです。

Target frontierはJRA-VANのデータを元に独自データベースとして機能しているわけですから、過度にSQLにこだわる必要もなさそうです。

ただあちらはC言語用のテキストデータなのでより高速なのは間違いないです。

# horseIDはCSV横断検索、receIDはSQL検索
import mysql.connector,glob,csv,re,datetime,sys

config = {
  'user': 'root',
  'password': 'root',
  'host': 'localhost',
  'port': 3306,
  'raise_on_warnings': True
}

print('検索したい馬名を入力してください')
name = input()

<中略>

print(f'検索馬のID {id} 誕生年 {id[0:4]}')

start_year = int(id[0:4]) + 2
print(f'推定デビュー年 {start_year}')

conn = mysql.connector.connect(**config)
cur = conn.cursor()

raceID_l = list()
for y in range(start_year,2017 +1):
	table = f'race_r_{y}'

	try:
		cur.execute(f'SELECT raceID FROM horse_race_result.{table} WHERE horseID = {id}')

	finally:
		result = cur.fetchall()
		if result != []:
			raceID_l.append(result)

# ネストを平滑化する
raceID_l_flatten = [e for l in raceID_l for e in l]

print(f'出走数 {len(raceID_l_flatten)}')
print(raceID_l_flatten)

cur.close()
conn.close()
--------------------------------------------------

出力
--------------------------------------------------
検索したい馬名を入力してください
キタサンブラック
[['201202013', 'キタサンブラック']]
検索馬のID 201202013 誕生年 2012
推定デビュー年 2014
出走数 20
[('r201505010105',), ('r201505010807',), ('r201505021210',), ('r201506020811',), ('r201506030811',), ('r201506040511',), ('r201506050810',), ('r201508040711',), ('r201605050811',), ('r201606050910',), ('r201608030411',), ('r201608040311',), ('r201609020411',), ('r201609030811',), ('r201705040911',), ('r201705050811',), ('r201706050811',), ('r201708030411',), ('r201709020411',), ('r201709030811',)]

[Python] 235 MySQL 03 CSVを一括インポートする

1万を超えるレースデータを一括インポートしてみました。

実際は最初のfor文を回さず年ごとに3回に分けてインポートしました。1〜2KB約3400ファイルで2分掛からなかった感じです。

インポート直後にはphpMyAdminがダウンしますが、MAMPを再起動すると正常に動きました。

私が扱うデータ量程度ではMySQLのキャパを超えることはないと思います。

これからせっせとデータを入れていきます。

import csv,mysql.connector,glob
import pandas as pd

config = {
    'user': 'root',
    'password': 'root',
    'host': 'localhost',
    'port': 3306,
    'raise_on_warnings': True
}

for year in range(2015,2017 +1):
    # DB化するCSVファイルのパスとTable名(拡張子なしのファイル名)をリスト化する
    file_l = list()
    table_l = list()
    for file in glob.glob(f'/horse_racing/race_result/{year}/*/*/*.csv'):
        file_l.append(file)
        table_l.append(file[-23:-4])

    # CSVファイルの列タイトルをリストにする
    df = pd.read_csv(file_l[0], encoding='Shift-JIS',header = None)
    column_title = list(df.loc[0])

    column_type = ['varchar(10)','int(2)','int(2)','varchar(200)','varchar(200)','float','varchar(200)','varchar(200)','varchar(200)','varchar(200)','varchar(200)','varchar(10)','varchar(10)','varchar(10)','varchar(200)','varchar(200)','varchar(200)','varchar(200)','varchar(200)','varchar(200)','varchar(200)','varchar(13)','int(9)']

    # SQL文に使う列タイトルの文字列を作成する(角括弧を丸括弧に置換えるなど)
    column_l = list()
    for ti,ty in zip(column_title,column_type):
        column = ti + ' ' + str(ty)
        column_l.append(column)

    column_l_str = str(column_l).replace('[','(').replace(']',')').replace("'",'').replace('(万円)','')

    # sqlのリストを作成
    sql_l = list()
    for table in table_l:
        sql = f"create table horse_race_result.{table} {column_l_str}"
        sql_l.append(sql)

    # mysqlに接続
    conn = mysql.connector.connect(**config)
    cur = conn.cursor()

    # データベースhorse_race_resultにtableを作成する
    for file,table,sql in zip(file_l,table_l,sql_l):
        cur.execute(sql)
        cur.execute('begin')

        # CSVファイルを読み込み、各行をtableに挿入する
        with open(file, 'rt', encoding='Shift-JIS') as f:
            reader = csv.reader(f)
            for i,row in enumerate(reader):
                print(f'row {row}')
                if i != 0:
                    row_str = str(row).replace('[','(').replace(']',')')
                    print(row_str)
                    sql = f'insert into horse_race_result.{table} values {row_str}'
                    print(sql)
                    cur.execute(sql)

        cur.execute('commit')

    conn.close()

[Python] 234 複数のCSVファイル内検索 馬名からレース検索 リスト内包表記

一番時間の掛かるraceID検索のところをリスト内包表記で書いてみました。

そもそもopen文を組み込むことができるのか懐疑的だったものの、あっさり書けました。こんなややこしいのを共同開発の現場で見せられたらたまりませんね。

残念ながら時間短縮はならずです。C言語で書いてもそんなに変わらなさそうですが、短いコードなので検討してみても面白そうです。

元コード for文
--------------------------------------------------
raceID_l = list()
for y in range(start_year,2021 +1):
    for file in glob.glob(f'/horse_racing/race_result/{y}/*/*/*.csv'):
        with open (file, mode="r", encoding="shift_jis") as f:
            reader = csv.reader(f)
            for row in reader:
                if row[22] == id:
                    raceID_l.append(row[21])
--------------------------------------------------

リスト内包表記
--------------------------------------------------
raceID_l = [row[21] for y in range(start_year,2021 +1) \
for file in glob.glob(f'/horse_racing/race_result/{y}/*/*/*.csv') \
for row in csv.reader(open(file,mode="r", encoding="shift_jis")) \
if row[22]==id]
--------------------------------------------------

[Python] 233 複数のCSVファイル内検索 馬名からレース検索 高速化

前回のコードは見るからに効率の良くなさそうな内容だったので書き直しました。

pandasはやめてcsvモジュールで処理しました。

その結果、80秒が5秒に短縮されました。やはりこの種の処理にpandasを使うのはナンセンスだったようです。

この速さなら本式のデータベースにする程でもないように思いますが、迷うところです。

import glob,csv,re,datetime

print('検索したい馬名を入力してください')
name = input()

horseID_l = list()
for year in range(1986,2019 +1):
    for file in glob.glob(f'/Volumes/DATA_HR/horse_racing/horse_list/horse{year}.csv'):
        with open (file, mode="r", encoding="shift_jis") as f:
            reader = csv.reader(f)
            for row in reader:
                if row[1] == name:
                    horseID_l.append([row[0],row[1]])

print(f'{horseID_l}')

if len(horseID_l) >= 2:
    print('該当する馬が複数います。番号を入力してください。')
    for i,data in enumerate(horseID_l):
        print(f'{i+1} {data}')
    num = input()
    id = horseID_l[int(num)-1][0]

elif len(horseID_l) == 0:
    print('該当する馬はいません')
    sys.exit()

else:
    id = horseID_l[0][0]

print(f'検索馬のID {id} 誕生年 {id[0:4]}')

raceID_l = list()
start_year = int(id[0:4]) + 2
print(f'推定デビュー年 {start_year}')
for y in range(start_year,2021 +1):
    for file in glob.glob(f'/horse_racing/race_result/{y}/*/*/*.csv'):
        with open (file, mode="r", encoding="shift_jis") as f:
            reader = csv.reader(f)
            for row in reader:
                if row[22] == id:
                    raceID_l.append(row[21])

print(raceID_l)

race_l = list()
for id in raceID_l:
    for y in range(start_year,2021 +1):
        for file in glob.glob(f'/horse_racing/race_name/{y}/*.csv'):
            with open (file, mode="r", encoding="shift_jis") as f:
                reader = csv.reader(f)
                for row in reader:
                    if row[9] == id:
                        date = row[0]
                        racename = row[3]
                        date_dt = datetime.datetime.strptime(date,'%Y年%m月%d日')
                        date_d = datetime.date(date_dt.year, date_dt.month, date_dt.day)

                        race_l.append([id,date,racename,date_d])

print(f'出走数 {len(race_l)}')
print(sorted(race_l, key=lambda x: x[3]))