Tkinterの使い方:after で処理を「遅らせて」or 処理を「定期的」に実行する

このページでは Python の tkinter に用意された after メソッドの使い方について解説していきます。

after メソッドは全ウィジェットのクラスに用意されたメソッドであり、after メソッドを利用することで下記を行うことが可能になります。

  • 特定の処理を指定した時間分遅らせて実行する
  • 特定の処理をある時間ごとに定期的に実行する

time.sleep でも上記は実現できそうですが、after メソッドは time.sleep とは大きく異なる点があります。こういった time.sleep との違いについても解説していきたいと思います。

after メソッド

まずは after メソッドがどんなメソッドであるかを解説していきます。

after メソッドの定義

after メソッドの定義は下記のようになっています。

afterメソッド
after(self, ms, func=None, *args)

after メソッドの引数

after メソッドに実行時に指定する引数は下記の3つになります。

  • ms(第1引数):どれだけ遅らせて実行するかの時間(単位はミリ秒)
  • func(第2引数):ms 経過後に実行する関数(メソッド)
  • args(第3引数):func に渡す引数

after メソッドの戻り値

after メソッドを実行すると、戻り値として ID が返却されます。

この ID は後に説明する after_cancel メソッドによる after の取り消しで説明する after_cancel メソッドで利用します。

スポンサーリンク

after メソッドとは

after メソッドは、処理を遅らせて実行するメソッドです。

after メソッドの動作

after メソッドを実行すると、after メソッド実行してから第1引数 ms で指定した時間(ミリ秒)経過後に第2引数 func で指定した関数が自動的に実行されます。

after メソッドの処理イメージ

つまり、第1引数 func で指定した関数を引数 ms で指定した時間分、遅らせて実行させることができます。もちろん第1引数には関数だけでなく、メソッドも指定することが可能です。

「後で処理させる」ので、メソッド名が「after」であると考えると覚えやすいと思います。

after メソッドで行われるのは登録のみ

after メソッド実行時に行われるのは、あくまでイベント処理の登録(何秒後に何の処理を行うかの登録)のみであり、after メソッド内で ms で指定した時間待たされるわけではありません。

ですので、after メソッドを実行すると、すぐに after メソッド自体は終了します。

after メソッドはすぐに終了しますので、after メソッド実行後に mainloop を実行すれば、すぐにマウスクリックやキーボードのキー入力などのイベント処理を行うことも可能ですし、after メソッド実行後にすぐに他の処理を実行することも可能です。

この辺りは time.sleep と大きな違いになります。after と time.sleep との違いの詳細については after メソッドと time.sleep との違いで解説したいと思います。

処理は mainloop 内から自動的に実行される

そして、after メソッドで遅らせて実行することを指定した関数は、時間経過後に自動的に実行されることになります。

この遅らせて実行される関数やメソッドは、mainloop メソッドの中から実行されます(実行するのは tkinter 本体)。

つまり、mainloop メソッドを実行しないと after メソッドを実行しても、after メソッドの第2引数で指定した関数やメソッドは自動的に実行されませんので注意してください。

MEMO

after メソッドを実行して時間経過後にupdate メソッドを実行することで強制的に after メソッドの第2引数で指定した関数やメソッドを実行することも可能です

after メソッドが実行できるウィジェット

前述の通り、after メソッドは tkinter の全ウィジェットに用意されているメソッドになります。

after メソッドの使い方

続いては after メソッドのサンプルスクリプトを見ながら after メソッドの使用例を紹介していきたいと思います。

サンプルスクリプト

下記スクリプトは after メソッドの簡単な使用例になります。

afterメソッドの使用例
# -*- coding:utf-8 -*-
import tkinter

# 遅らせて実行される関数
def func():
	global label

	# ラベルのテキストを変更
	label.config(
		text="3000ms経過しました",
	)


# メインウィンドウの作成
app = tkinter.Tk()
app.geometry("300x200")

# ラベルウィジェット作成
label = tkinter.Label(
	app,
	width=15,
	height=1
)
label.pack()


# 3000ms後にfunc関数を実行
app.after(3000, func)

# メインループ
app.mainloop()

スクリプトを実行すると、下記のようなアプリが起動します。起動時にはアプリ上に何も表示されません。

afterの使用例1

起動後、3秒経過すると下記のようにアプリ上に文字列が表示されます。

afterメソッドの使用例2

サンプルスクリプトの説明

after メソッドを実行しているのは下記部分になります。

afterメソッドの実行
# 3000ms後にfunc関数を実行
app.after(3000, func)

after メソッドを実行しているのはメインウィンドウの app です。

前述の通り、tkinter の全ウィジェットに after メソッドが用意されていますので、他のウィジェット(例えば上のサンプルスクリプトであれば label)に実行させても良いです。

第1引数で 3000、第2引数で func を指定して after メソッドを実行していますので、after メソッド実行して 3000 ms 経過した時点でfunc が実行されることになります。

func 関数ではアプリ上に配置された label の文字列を "3000ms経過しました" に設定する処理(config メソッドで設定)を行っていますので、結果的に after メソッド実行してから 3000 ms 後にアプリ上に文字列が表示されることになります。

また、下記で mainloop を実行している点も after メソッドを利用する点で重要なポイントになります。

前述の通り、after メソッドの引数で指定した関数やメソッドは mainloop の中から自動的に実行されます。after メソッドを利用する場合は mainloop を実行するのを忘れないよ湯にしましょう(tkinter を利用しているのであれば基本的に mainloop は必ず実行すると思いますが…)。

mainloopメソッドの実行
# メインループ
app.mainloop()

実行する関数への引数の渡し方

after メソッドにより遅らせて実行される関数(第2引数 funcで指定する関数)には引数を渡すことも可能です。

この場合は after メソッドの第3引数以降に引数として渡したいデータを指定します。辞書形式で渡しても良いです。

引数を渡すことで、after メソッドにより遅らせて実行される関数により詳細な処理を記述することができるようになります。

引数を渡すサンプルスクリプト

下記スクリプトは after メソッドで遅らせて実行する関数に引数を渡す簡単な例になります。

関数に引数を渡す例
# -*- coding:utf-8 -*-
import tkinter

# 遅らせて実行される関数
def func(kw):
	global label

	# ラベルのテキストを変更
	label.config(
		text=kw['text'],
		bg=kw['bg'],
	)


# メインウィンドウの作成
app = tkinter.Tk()
app.geometry("300x200")

# ラベルウィジェット作成
label = tkinter.Label(
	app,
	width=15,
	height=1
)
label.pack()

# 関数に渡す辞書を作成
kw = {
	"text":"3000ms経過しました",
	"bg":"orange"
}

# 3000ms後にfunc関数を実行
app.after(3000, func, kw)

# メインループ
app.mainloop()

after メソッドの使い方とほぼ同じスクリプトですが、起動して3秒後に表示されるアプリの画面は下の図のようになります。

関数に引数を渡す例

サンプルスクリプトの説明

after メソッドを実行しているのは下記部分で、第3引数に kw を指定するようにしています。

afterメソッドの実行
# 3000ms後にfunc関数を実行
app.after(3000, func, kw)

この kw は下記で作成した辞書データになります。

引数で渡すデータの作成
# 関数に渡す辞書を作成
kw = {
	"text":"3000ms経過しました",
	"bg":"orange"
}

上記のように第3引数を指定して after メソッドを実行すれば、第2引数(func)の関数が実行される際に、その関数に第3引数のデータ(kw)が引数として渡されるようになります。

その実行される func は下記のようになります。

実行される関数の定義
# 遅らせて実行される関数
def func(kw):
	global label

	# ラベルのテキストを変更
	label.config(
		text=kw['text'],
		bg=kw['bg'],
	)

引数を受け取れるように関数を定義し、その引数を辞書データとしてアクセスして利用しているところがポイントです。

スポンサーリンク

after メソッドで処理を遅らせて実行する方法

ここからは after メソッドの実用的な使い方について解説していきたいと思います。

1つ目はこの「after メソッドで処理を遅らせて実行する」使い方です。

といっても、この使い方は after メソッドの最も一般的な使い方であり、ここまでの after メソッドに関する解説の中でもこの使い方は紹介しています。

after メソッドで処理を遅らせて実行する」ためには、after メソッドを実行すれば良いだけです。

使用方法に関しても既に after メソッドの使い方実行する関数への引数の渡し方でサンプルスクリプトを用いて解説していますので、こちらを参考にしていただければと思います。

after メソッドで処理を定期的に実行する方法

続いて「after メソッドで処理を定期的に実行する」使い方について解説していきます。

処理を定期的に実行する考え方

ここまで解説してきた通り after は特定の処理(第2引数で指定した関数やメソッド)を遅らせて実行するメソッドです。

その遅らせて実行される処理は、 after メソッドを1回実行するにつき、1回のみ実行されます。回数は指定できません。

ですので、単に1度だけ after メソッドを実行するだけでは「処理を定期的に実行する」は実現できません。

しかし、下記のようにプログラミングしてやることで、「after メソッドで処理を定期的に実行する」を実現することができます。

  • after メソッドにより遅らせて実行される処理の中で、再度 after メソッドを実行する

例えば定期的に行いたい処理を repeat_func 関数だとすれば、その repeat_func 関数の中に「定期的に行いたい処理」だけでなく「after メソッドの実行処理」も記述します。

この時、after メソッドの第2引数には再度、定期的に行いたい処理である repeat_func を指定します。

def repeat_func():
	# 定期的に実行したい処理を記述

	# 再度afterを実行
	# appはメインウィンドウのインスタンス
	app.after(100, repeat_func)

これにより、repeat_func 関数実行された際に、after メソッドが実行され、再度遅らせて repeat_func 関数が実行されることになります(上の例では 100 ms 後に実行される)。

次に repeat_func 関数が実行された際にも、after メソッドにより repeat_func 関数が遅らせて実行されることになります。これが繰り返されるので、結果的に repeat_func が定期的に実行されることになります(上の例では 100 ms 間隔 で処理が定期的に実行される)。

下の図は after メソッドにより func 関数を定期的に実行するイメージになります。

afterメソッドで定期的に処理を実行するイメージ

スポンサーリンク

サンプルスクリプト

次は実際にサンプルスクリプトを用いて説明していきたいと思います。

下記は after メソッドを利用して定期的に処理を行う簡単な例になります。

スクリプト実行からの時間を1秒ごとにカウントアップし、そのカウントアップした値をアプリ上のラベルに表示する例になります。

定期的に処理を行う例
# -*- coding:utf-8 -*-
import tkinter

# 秒数をカウントする変数
count = 0

# 定期的に実行する関数
def repeat_func():
	global app
	global label
	global count

	# 定期的に行いたい処理
	count += 1
	label.config(
		text=str(count)
	)

	# 再度repeat_funcが実行されるようにafter実行
	app.after(1000, repeat_func)

# メインウィンドウの作成
app = tkinter.Tk()
app.geometry("300x200")

# ラベルウィジェット作成
label = tkinter.Label(
	app,
	width=15,
	height=1,
	text="0",
	font=("", 50)
)
label.pack()

# 1000ms後にrepeat_func関数を実行
app.after(1000, repeat_func)

# メインループ
app.mainloop()

スクリプトを実行するとアプリが起動し、下図のような画面が表示されます。起動時にはラベルには “0” が表示されます。

afterによる定期的な処理の実行例1

そして起動後1秒ごとにラベルに表示される数字がカウントアップされていきます。

afterによる定期的な処理の実行例2

サンプルスクリプトの解説

最初に after メソッドを実行しているのは下記部分です。これにより after メソッドを実行してから 1000 ms 後に repeat_func 関数が実行されることになります。

afterメソッドの実行1
# 1000ms後にrepeat_func関数を実行
app.after(1000, repeat_func)

repeat_func 関数は下記のようになっています。

repeat_func関数
# 定期的に実行する関数
def repeat_func():
	global app
	global label
	global count

	# 定期的に行いたい処理
	count += 1
	label.config(
		text=str(count)
	)

	# 再度repeat_funcが実行されるようにafter実行
	app.after(1000, repeat_func)

ポイントは repeat_func 関数内で after メソッドを実行している下記部分です。

afterメソッドによる定期的な処理
# 定期的に実行する関数
def repeat_func():
	# 〜略〜

	# 再度repeat_funcが実行されるようにafter実行
	app.after(1000, repeat_func)

after メソッドの第2引数で repeat_func 関数を指定しているため、再度 1000 ms 後に repeat_func 関数が実行されることになります。

以降、その repeat_func 関数実行時には同様の処理が行われるため、1000 ms 毎に定期的に repeat_func 関数を実行することができます。

こんな感じで、repeat_func 関数に「after メソッドの実行」+「実行させたい処理」を記述しておけば、定期的に「実行させたい処理」が実行されるようになります。

定期的に行わせる処理
# 定期的に実行する関数
def repeat_func():
	# 〜略〜

	# 定期的に行いたい処理
	count += 1
	label.config(
		text=str(count)
	)

	# 再度repeat_funcが実行されるようにafter実行
	app.after(1000, repeat_func)
MEMO

mainloop から自動的に関数が実行された場合、その関数終了時に再び自動的に mainloop に処理が戻ります

ですので、mainloop は最初に一度だけ実行しておけばよく、関数内で mainloop を再度実行するようなことは不要です

after_cancel メソッドによる after の取り消し

次は after_cancel メソッドと一緒に覚えておくと良い after メソッドについて解説します。

スポンサーリンク

after_cancel とは

after メソッドを実行することで after メソッドに指定した関数を遅らせて実行させられるのはここまで解説してきた通りです。

実際には、(これも簡単に説明しましたが)after メソッド自体でその関数が実行されるのではなく、指定した時間後に関数が実行されるように予約される(登録される)だけです。

after_cancel メソッドは、その予約をキャンセルするメソッドです。

例えば単に定期的に処理を実行するようにすると、無限にその処理が実行されてしまいますが、after_cancel メソッドを実行することでその定期的な処理を途中で停止させることもできます。

after_cancel メソッドの定義

after_cancel メソッドは下記のように定義されています。

after_cancelの定義
after_cancel(self, id)

実行時に指定する引数は id であり、戻り値はありません(None)。

引数で指定する id は具体的には after メソッドの戻り値となります。

after メソッドは戻り値として id を返却しますので、キャンセルしたい after メソッドの戻り値を after_cancel 実行時に引数として指定すれば良いです。

after_cancel メソッドの使い方

下記は after_cancel メソッドの簡単な使用例になります。

after_cancelの使用例
# -*- coding:utf-8 -*-
import tkinter

# 秒数をカウントする変数
count = 0

after_id = None

def stop_func():
	global app
	global after_id
	app.after_cancel(after_id)

# 定期的に実行する関数
def repeat_func():
	global app
	global label
	global count
	global after_id

	# 定期的に行いたい処理
	count += 1
	label.config(
		text=str(count)
	)

	# 再度repeat_funcが実行されるようにafter実行
	after_id = app.after(1000, repeat_func)

# メインウィンドウの作成
app = tkinter.Tk()
app.geometry("300x200")

# ラベルウィジェット作成
label = tkinter.Label(
	app,
	width=15,
	height=1,
	text="0",
	font=("", 50)
)
label.pack()

# ボタンウィジェット作成
button = tkinter.Button(
	app,
	text="STOP",
	font=("", 50),
	command=stop_func
)
button.pack()

# 1000ms後にrepeat_func関数を実行
after_id = app.after(1000, repeat_func)

# メインループ
app.mainloop()

基本的な作りは after メソッドで処理を定期的に実行する方法で紹介したスクリプトと同じで、アプリを起動すると after により定期的に repeat_func 関数が実行され、ラベルが1秒毎にカウントアップされていきます。

さらに上記スクリプトではボタンを表示するようにしており、ボタンを押すと after_cancel メソッドが実行されて、その after による repeat_func 関数の実行をキャンセルされ、カウントアップが停止します。

スポンサーリンク

after メソッドと time.sleep との違い

ここまで解説した内容を見て、「これ time.sleep でもいいんじゃないの?」と思われる方もいるかもしれないですが、after と time.sleep は全く異なるものになります。

time.sleep 関数

time.sleep は、引数で指定した秒数分プログラムを停止させる(より正確にいうとスレッドを停止させる)関数になります。

time.sleep を実行すると、time.sleep 関数の中で引数で指定した秒数分、time.sleep 関数の中で待たされます。つまり、その時間の間 time.sleep 関数は終了せず、そのプログラムでは他の処理を実行することができます。

sleepとafterの違い1

例えば、time.sleep でプログラムが停止している間は、マウスのクリックやキーボードのキー入力を受け付けてイベント処理を行うようなことはできません(そもそも sleep を実行するとプログラムが停止するので sleep が終わるまで mainloop を実行することができない)。

after メソッド

一方で、after メソッドではプログラムが停止しません。after メソッドでは「何ミリ秒後」に「何の関数」を実行するかの情報を登録するだけであり、after メソッドの中で待たされるようなことはありません。

ですので、「after メソッド実行〜引数で指定した関数が実行されまで」の間でもプログラムはしっかり動作しており、マウスのクリックやキーボードのキー入力を受け付けてイベント処理を行うようなこともできます(mainloop メソッドを実行しておく必要はあります)。

sleepとafterの違い2

特に tkinter で作成するような GUI アプリは、ユーザーから様々なイベントを受け付け、そのイベントに応じて処理を即座に実行してユーザに機能やサービスを提供することが重要です。

time.sleep でプログラムを停止してしまうと、停止している間ユーザーからイベントを受け付けてもその処理が実行できず、ユーザーを変に待たせてしまうことになる操作性が悪く、ユーザーにストレスを与えてしまいます。

特に tkinter においては time.sleep よりも after メソッドを利用することをオススメします。

スポンサーリンク

after メソッドの注意点

最後に after メソッドの注意点について解説しておきます。

after メソッドを使いすぎると負荷が高くなる

まず一つ目の注意点は「after メソッドを使いすぎると負荷が高くなる」ことです。

特に after メソッドを利用して短い期間で定期的に重い処理(時間のかかる処理)を実行するときの注意点になります。

after メソッドを実行するのは結局は CPU です。

CPU の処理能力を超えて定期的に処理を実行すると、CPU 使用率が一気に100%になって処理が追いつかなくなってしまいます。

afterを使用しすぎてCPUが負荷大になるイメージ

CPU の使用率はアクティブモニターやタスクマネージャーで確認できますので、アプリ実行時に CPU の使用率が上がりすぎていないかを確認すると良いと思います。

CPU使用率の確認

CPU 使用率が上がっている場合は、下記で修正できないかを検討してみると良いと思います。

  • after メソッドの第1引数に指定する時間を長くする
  • after メソッドの第2引数に指定する関数の処理を軽くする

私の経験的に、after メソッドを利用して tkinter の Canvas で図形の描画を大量に行うとすぐに CPU 使用率が 100% 付近まで上がってしまいました…。

指定された時間に処理が実行されるとは限らない

二つ目の注意点は「指定された時間に処理が実行されるとは限らない」ことです。

何度も書いているのですが、あくまでも after メソッド実行時に登録された関数が自動的に実行されるのは mainloop 実行中です。

アプリが暇なときは基本的にそのアプリの Python スクリプトでは mainloop が実行されています。ですので、アプリが暇なときは基本的に after メソッド実行時に登録された関数は指定した時間後に自動的に実行されます。

ただし、アプリが忙しいとき(例えばユーザーからのマウスクリックやキーボードのキー入力等に応じて何らかの処理を実行しているとき)は基本的に mainloop 実行中ではありません。例えば他のイベント処理を実行してたりします。

このとき、 after メソッド実行時に登録された関数は、次に Python スクリプトで mainloop が実行された時になります。つまり、次に mainloop が実行されるまで after メソッド実行時に登録された関数の実行開始が遅れることになります。

関数の実行が遅れるイメージ

ですので、この遅れを無くすように各処理を軽くしたり、遅れが発生しても問題ないようにアプリを設計することが重要です。

スポンサーリンク

まとめ

このページでは after メソッドについて解説しました。

after メソッドを利用することで、処理を遅らせて実行したり、処理を定期的に実行することができます。

また time.sleep とは異なり、プログラムが停止するようなこともないため、処理が実行されるまでの間もイベント処理を行うことも可能です。

tkinter で GUI アプリ開発で利用するとめちゃめちゃ便利なメソッドだと思います。例えば「テトリスアプリでブロックを定期的に落下させる」みたいに定期的に処理を実行したい場合は必須級のメソッドです!

是非この機会に使い方をマスターしてください!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です