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

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

ただいまの
回答率

88.91%

def関数を別のdef関数から停止させたい

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 258

OkukawaRyoki

score 9

現在pythonのtkinterを用いてプログラムを実行停止するようなアプリを作っているのですが、
実行中のdef関数を別のdef関数から停止させることは可能ですか?

def button_start():
    while True:
        #各種処理

def button_stop():
    sys.exit(0) #的な?

# Startボタン
    button2 = ttk.Button(frame2, text='Start', command=button_start)
    button2.pack(side=LEFT)

# Cancelボタン
    button3 = ttk.Button(frame2, text='Cancel', command=button_stop)
    button3.pack(side=LEFT)


大雑把にこんな感じで書いたのですが、予想通り止めることはできず、キャンセルボタンを押すと
tkinterのguiが応答なしになります。(pythonプログラムは実行されている)

global変数でフラグを作ってdef button_startの中にif文(フラグ判定)で終了させるようなことも試したのですが
うまくいかず、pyhtonはまだ勉強したてなので
どうかご教授を、、、

調べてもこれ!!って感じの見つからなくて質問したほうが早いかなぁ、、、なんて

よろしくお願いいたします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 2

checkベストアンサー

+1

button_start がメインスレッド上で実行されるので、
while ループが終わるまでは他の処理は実行されません。

メインループ → button_start → メインループ →

順番に実行されるので、
button_start 内で時間の掛かる処理を行い、
メインループに処理が戻らないと応答なしになります。

GUI等のイベント駆動型プログラミングで、よくある誤解なのですが、
イベントハンドラの関数(button_start)は、
勝手に裏で同時に実行される訳ではありません。

イベントハンドラ内でループを使う場合は、
スレッドを使う等が一般的な解決策です。
他に幾つか解決案を紹介します。


  1. ウインドウが固まらないように、ループ内で定期的に update() を呼ぶ
    これは本当に一時的な回避策。使える状況は限られますが、有効な場合も有。

  2. 別スレッド/プロセスを作り、ループを外部から中断できるフラグを仕込む。
    y_waiwaiさん提案の方法に該当します。

  3. タイマーを使う
    after や after_idle を使って、
    イベントループの合間に任意の処理を実行します。
    但し、1回の処理は、直ぐに終わる程度に分割が必要です。

  4. ジェネレーター(とタイマー)を使う
    同上ですが、ジェネレータを使ってループ文で記述可能な方法。

  5. 非同期ライブラリ (asyncio等) を使う


1と3、4 は、ネットワークやデータベースが絡む処理、
時間の掛かる計算処理には向きません。
簡単なアニメーション等やスケジューリングの用途には使えます。

2 のスレッドは汎用的な方法ですが、別スレッドからGUIにアクセスする際は、
もうひと工夫が必要なので注意。

「各種処理」の内容次第では、適切な解決策は変わってきます。

1, 2, 3 は探せばサンプルコードは見つかると思いますので、
4 の方法のみコードを紹介します。
5 は選択肢として言及したものの、長くなるので割愛。
興味があれば、「非同期プログラミング」について調べて見て下さい。

import tkinter as tk
from tkinter import ttk


def after_timed_gen(root, gen, done=None, _stop=None):
    """
    ジェネレーターをTkinterのタイマーで消化する関数
    after() に指定する 遅延ミリ秒を yield で指定できます。
    """

    def next_gen():
        interval = next(gen, _stop)
        if interval is not _stop:
            root.after(interval, next_gen)
        else:
            if done:
                done()

    root.after_idle(next_gen)
    return gen


if __name__ == "__main__":
    root = tk.Tk()
    label = ttk.Label(root)
    label.pack()

    def countUp(count=0):
        while True:
            count += 1
            label.config(text="Count: {}".format(count))

            # メインスレッド内で GUIの応答を止めることなく、
            # time.sleep(1) としたい場合の代替策です。
            yield 1*1000

    gen = None

    def button_start():
        global gen
        gen = after_timed_gen(root, countUp())

    def button_stop():
        global gen
        if gen:
            gen.close()
            gen = None


    button1 = ttk.Button(root, text='Start', command=button_start)
    button1.pack()

    button2 = ttk.Button(root, text='Cancel', command=button_stop)
    button2.pack()

    root.mainloop()

スレッドを使う方法の停止・中断デモ source
※ブラウザ上で実行できます

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/07/24 08:19

    ご回答ありがとうございます。
    GUIプログラムは初めて書くのでとっても参考になりました。
    現在作っているプログラムは処理が長いのでスレッド型を採用したいと思います。
    またその他の解決案も一つ一つ、実践してみようと思います。

    この度は本当にありがとうございます!

    キャンセル

-3

グローバル変数でbool変数を定義しておき、
ループの実行をその変数で回すようにしとけば、
もう片方の関数で、その変数をfalseにすれば、そのループを止めれますね

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/07/23 16:31

    ご回答ありがとうございます。

    if文のフラグ判定では止められないのなぜでしょうか?
    よろしければ教えていただけると嬉しいです

    キャンセル

  • 2020/07/23 16:39

    if文とはどこのはなしでしょうか
    提示のコードには見当たりませんが。

    キャンセル

  • 2020/07/23 16:44

    提示していませんでした、すみません。
    例えになってしまいますが、for文の中でif文のフラグ判定によってプログラムを停止させるようなことをが出来ればと思ったのですが実行したところ応答なしになってしまうので何か理由があるのかなと思いまして。

    キャンセル

  • 2020/07/23 16:55

    そのコードを提示してもらわないことにはなんとも言えません。
    あなたのコードが間違っている、としかいえませんよ

    キャンセル

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

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

関連した質問

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