質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.59%

messageboxを閉じたときに、冒頭の処理に戻るようにしたい

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 1,424
退会済みユーザー

退会済みユーザー

前提・実現したいこと

python3でクイズプログラムの作成を行っています。
1.取り込んだcsvから、問題に使う植物Aをランダムに選択
2.Aと同じ科の植物の中から別のひとつBを選択(答えとなる)
3.Aと異なる科の植物の中から3つを選択(C,D,E)
4.「Aと同じ科の植物はどれか選べ」という問題に
B,C,D,Eの選択肢を提示する
5.回答を選んで答え合わせボタンを押すと、正解不正解を表示
6.OKボタンを押すと再び1.に戻って新たな問題が表示される
というプログラムを想定しています。

みなさんのご指導のおかげで5までは実現できたのですが、
6の処理で三度行き詰ってしまいました。
1~4の処理をひとつの関数にまとめて、ボタンクリックイベントの下につけるという
発想がナンセンスなのでしょうか?連日恐縮ですがご教示ください。

該当のソースコード

import random
import csv
import tkinter, tkinter.messagebox

# 問題のもととなるcsvファイルを読み込み
# csvは1行目に name,family,character
#      2行目に カブ,アブラナ科,春の七草
#      3行目以降 キキョウ,キキョウ科,秋の七草
# といった感じで3列の値が並ぶ

with open('plant.csv', newline='') as csvfile:
    reader = csv.DictReader(csvfile)
    rows = []
    for row in reader:
        rows.append(row)

# 上記DictReaderで読み込んだ辞書リストから、問題用に、ひとつの辞書を呼び出す
p_plant = random.choice(rows)
print(p_plant)
p_name = p_plant["name"]

# 問題用の植物と同じ科(family)の植物をリストにして、
# その中から問題用の植物を削除。
# 残りからひとつを選ぶ(=その植物が答えになる)

q_list = [x['name'] for x in rows if x['family'] == p_plant["family"]]
q_list.remove(p_plant["name"])
answer = random.choice(q_list)

# 答えとは異なる科(family)の植物をリスト化し、そこから3つ選ぶ

b_list = [x['name'] for x in rows if x['family'] != p_plant["family"]]
b_options = random.sample(b_list , 3)
b_options.append(str(answer))

tki = tkinter.Tk()
tki.geometry('300x400')
tki.title('ラジオボタン')

# ラジオボタンのラベルをリスト化する
rdo_txt = random.sample(b_options , k=4) 
# ラジオボタンの状態
rdo_var = tkinter.IntVar()

# ラジオボタンを動的に作成して配置
for i in range(len(rdo_txt)):
    rdo = tkinter.Radiobutton(tki, value=i, variable=rdo_var, text=rdo_txt[i]) 
    rdo.place(x=50, y=30 + (i * 24))

# ボタンクリックイベント
def btn_click():
    num = rdo_var.get()

    if rdo_txt[num] == str(answer):
        tkinter.messagebox.showinfo('結果' , "正解!")
    else:
        tkinter.messagebox.showinfo("結果" , '不正解!正解は' + answer)

# ボタン作成 
btn = tkinter.Button(tki, text='答え合わせ', command=btn_click)
btn.place(x=100, y=170)

var = tkinter.StringVar()
var.set(p_name + "と同じ科の植物はどれでしょう")
label = tkinter.Label(tki, textvariable=var, width=100, )
label.pack()

tki.mainloop()

試したこと

<1>
p_plant = random.choice(rows)~
以降を関数にしてボタンクリックイベントの下につけようとしましたが
3行目以降(p_nameの定義)が動きませんでした。
<2>
ボタンクリック後のshowinfoの戻り値をきっかけに
冒頭のrandom.choice(rows)に戻るループを何回かチャレンジしましたが
全くうまくいきませんでした。

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

1~4の処理をひとつの関数にまとめて、ボタンクリックイベントの下につけるという
発想がナンセンスなのでしょうか?

恐らくまとめ方に問題があったのだと思います。オリジナルのコードですと、最初に配置済みのボタンやラジオボタン、ラベルの扱いにも困るでしょう。1回だけ実行すれば良い部分と、メッセージボックスに応答した後に再初期化しないといけない部分をうまく切り出せば、その方法でもいけます。
なるべく質問者さんのコードをそのまま利用し、適当に関数にまとめ、関数間でやり取りする変数はグローバル変数として使うよう修正した例が以下です。

import random
import csv
import tkinter, tkinter.messagebox

tki = None
rdo = None
rdo_txt = None
rdo_var = None
answer = None

def init_view(tki):
    global rdo, rdo_txt, rdo_var, answer

    # 上記DictReaderで読み込んだ辞書リストから、問題用に、ひとつの辞書を呼び出す
    p_plant = random.choice(rows)
    print(p_plant)
    p_name = p_plant["name"]

    # 問題用の植物と同じ科(family)の植物をリストにして、
    # その中から問題用の植物を削除。
    # 残りからひとつを選ぶ(=その植物が答えになる)

    q_list = [x['name'] for x in rows if x['family'] == p_plant["family"]]
    q_list.remove(p_plant["name"])
    answer = random.choice(q_list)

    # 答えとは異なる科(family)の植物をリスト化し、そこから3つ選ぶ
    b_list = [x['name'] for x in rows if x['family'] != p_plant["family"]]
    b_options = random.sample(b_list , 3)
    b_options.append(str(answer))

    # 配置済みの子ウィジェットが存在すれば、削除しておく
    children = tki.winfo_children()
    for child in children:
        # print("type of widget is : " + str(type(child)))
        if str(type(child)) == "<class 'tkinter.Radiobutton'>":
            child.destroy()
        elif str(type(child)) == "<class 'tkinter.Button'>":
            child.destroy()
        elif str(type(child)) == "<class 'tkinter.Label'>":
            child.destroy()

    # ラジオボタンのラベルをリスト化する
    rdo_txt = random.sample(b_options , k=4) 
    # ラジオボタンの状態
    rdo_var = tkinter.IntVar()

    # ラジオボタンを動的に作成して配置
    for i in range(len(rdo_txt)):
        rdo = tkinter.Radiobutton(tki, value=i, variable=rdo_var, text=rdo_txt[i]) 
        rdo.place(x=50, y=30 + (i * 24))

    # ボタン作成 
    btn = tkinter.Button(tki, text='答え合わせ', command=btn_click)
    btn.place(x=100, y=170)

    var = tkinter.StringVar()
    var.set(p_name + "と同じ科の植物はどれでしょう")
    label = tkinter.Label(tki, textvariable=var, width=100, )
    label.pack()

# ボタンクリックイベント
def btn_click():
    num = rdo_var.get()

    if rdo_txt[num] == str(answer):
        tkinter.messagebox.showinfo('結果' , "正解!")
    else:
        tkinter.messagebox.showinfo("結果" , '不正解!正解は' + answer)

    # 表示内容の再初期化
    init_view(tki)


# 問題のもととなるcsvファイルを読み込み
# csvは1行目に name,family,character
#      2行目に カブ,アブラナ科,春の七草
#      3行目以降 キキョウ,キキョウ科,秋の七草
# といった感じで3列の値が並ぶ

with open('plant.csv', newline='') as csvfile:
    reader = csv.DictReader(csvfile)
    rows = []
    for row in reader:
        rows.append(row)

tki = tkinter.Tk()
tki.geometry('300x400')
tki.title('ラジオボタン')
init_view(tki)

tki.mainloop()

動くということを示すために簡単に修正して示してみましたが、以下の改善事項が思いつきます。ですので、もっと良い修正方法もあると思います。

  • グローバル変数は安易な気がする。(小さいプログラムならそれでもいいでしょうけど)
  • ラジオボタンなどのウィジェットの再初期化でdestroyしているが、数が決まっているのであれば表示文字列を変えるだけで良いはず。
  • WM_DELETE_WINDOWイベントをハンドリングして、メッセージボックス終了後に再初期化した方がプログラムの流れとして適切かもしれない。(できるかは未確認ですが)

ご参考になれば。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/01/22 21:00

    遅い時間に恐れ入ります。ありがとうございます。
    ご指摘の通り4択問題を想定していますので、ラジオボタンはdestroyせず、ラベルのみ更新することを検討してみます。
    ちなみにグローバル変数の使用を避けようとする場合は、関数間をまたいで読み込まれる関数を作らず、それぞれの関数の結果は平文(というのか、インデントのない部分)に返し、平文上で処理するのが目指す方向性となるのでしょうか?コメントのみで結構ですので、お時間あればご教示ください。

    キャンセル

  • 2020/01/23 01:54

    > グローバル変数の使用を避けようとする場合は
    についてですが、そうですね。入力は関数への引数、出力は関数からの返り値でやり取りするようなかたちになるかと思います。
    そうすると自然、teburiさんの言われる「インデントの無い部分に返す」ようになるはずです。ですが、グローバル変数もその内容や用途をよくよく検討してみると、クラス化したときのインスタンス変数として扱えるものであったりしますし、クラス化していない場合、グローバル変数でないとどうしても無理な部分も出てくるでしょう。

    グローバル変数をなるべく避けるのは関数間の結びつきを弱め、独立性を保つためですが、「プログラムは自分のやりたいことを叶えるひとつのツール」として考え直すと、現状で要望どおりに動いているものを無理に直すこともないかなとも思えます。(<「リファクタリング」と言って、見た目の動きは変えずに中身を積極的に直すこともあります)
    もし、今のものを大きく機能追加したり、別のプログラムを作る機会があったりしたときのテーマとして残しておくのでも良いかな、と少なくとも個人的には思います。

    キャンセル

  • 2020/01/23 20:50

    なるほど、プログラムが大きくなってくると、予期せぬところから変数に影響を与えたりするリスクが出そうですね。いったんはお言葉に甘えて(?)今のクイズプログラムの完成を目指しますが、以後念頭において勉強していきたいと思います。ご丁寧に再度のコメントありがとうございました!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.59%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る