関数やメソッドを指定した時間だけ遅らせて実行させることができるafterメソッドの使い方について解説します。afterメソッドを使えばボタンを押した後にディレイを入れたり、処理を再帰的に呼び出して自動更新処理を作ることができます。
目次
- afterメソッドとは
- 処理を遅らせて実行
- 自動更新処理(処理を再帰的に呼び出す)
- after_cancel メソッドによる after の取り消し
- afterメソッドだけでは完全なリアルタイム処理はできない
afterメソッドとは
afterは全てのウィジェットのクラスに用意されているメソッドで、次のように定義されています。
after(self, ms, func=None, *args)
- ms(第1引数):何ミリ秒遅らせて実行するか
- func(第2引数):実行する関数やメソッド
- args(第3引数):実行する関数やメソッドにわたす引数
afterメソッドが行うのは『〇〇ミリ秒後に指定した処理を実行するという登録』であり、after メソッド内で〇〇ミリ秒処理を待たされるわけではありません。
また、afterメソッドによって実行される関数やメソッドはmainloopメソッドの中から実行されます。
つまりmainloopメソッドを実行しないと、afterメソッドを実行しても指定した関数やメソッドは実行されません。
処理を遅らせて実行
実際にafterメソッドを使って処理を遅らせて実行してみましょう。
import tkinter as tk # tk.Frameを継承したApplicationクラスを作成 class Application(tk.Frame): def __init__(self, master=None): super().__init__(master) # ウィンドウの設定 self.master.title("ウィンドウのタイトル") self.master.geometry("600x100") # 実行内容 self.pack() self.create_widget() # create_widgetメソッドを定義 def create_widget(self): # label1ウィジェット self.label1 = tk.Label(self,text="「スイッチ」を押させるなーーーーッ", font=("MSゴシック", "20", "bold")) self.label1.pack() # button1ウィジェット self.button1 = tk.Button(self,text="いいや!限界だ押すね!", font=("MSゴシック", "20", "bold"), command=self.button1_clicked) # command引数にbutton1_clicked関数を渡す self.button1.pack() def button1_clicked(self): # button1をクリックした時の処理 self.label1["text"] = "シュゴォーーーーーーーッ" # label1ウィジェットのテキストを書き換え self.after(1000, self.button1_after1) # 1000ミリ秒後にbutton1_after1関数を実行 def button1_after1(self): self.label1["text"] = "ゴッ" # テキストを書き換え self.after(1000, self.button1_after2) # 1000ミリ秒後にbutton1_after2関数を実行 def button1_after2(self): self.label1["text"] = "オオオオオ" # テキストを書き換え self.after(1000, self.button1_after3) def button1_after3(self): self.label1["text"] = "戻れたぞ……フフフ" # テキストを書き換え if __name__ == "__main__": root = tk.Tk() app = Application(master=root) app.mainloop()
LabelとButtonを1つずつ配置し、ボタンをクリックするとLabelのテキストが変更されるプログラムです。
ただしボタンを押してテキストが変更された後、1秒経過すると自動的にテキストがまた書き換わり、その後1秒経過するとまた書き換わり…としてみました。
ボタンをクリックした後、1秒ごとにラベルの文字が書き換わっていくのがわかります。
自動更新処理(処理を再帰的に呼び出す)
先程の場合はボタンをクリックするという、ユーザーの操作からスタートする一連の処理でしたが、ユーザーが何もしなくても処理をし続けたい場合があります。
そんな時に便利なのが自動更新処理です。
afterメソッドは〇〇ミリ秒後に処理を登録できるので、その処理の中でまた同じ処理を呼び出してあげれば処理が無限に繰り返される無限ループを意図的に作り出すことができます。
試しに実行後からの経過秒数が表示されるプログラムを作ってみましょう。
import tkinter as tk # tk.Frameを継承したApplicationクラスを作成 class Application(tk.Frame): def __init__(self, master=None): super().__init__(master) # ウィンドウの設定 self.master.title("ウィンドウのタイトル") self.master.geometry("300x200") # 変数定義 self.elapse = 0 # 経過時間保存用の変数を定義 # 実行内容 self.pack() self.create_widget() # create_widgetメソッドを定義 def create_widget(self): # label1ウィジェットを作成 self.label1 = tk.Label(self,text=str(self.elapse) + "秒経過しました", font="24") # label1に表示したい文字を渡してインスタンスを生成 self.label1.pack() # label1ウィジェットを配置 # 毎秒の処理 def update_time(self): self.elapse += 1 # elapse変数を+1 self.label1["text"] = str(self.elapse) + "秒経過しました" # label1ウィジェットのテキストを書き換え app.after(1000, self.update_time) # 1000ミリ秒後にupdate_time関数を実行 if __name__ == "__main__": root = tk.Tk() app = Application(master=root) app.update_time() app.mainloop()
13行目で経過時間(秒)を保存するための変数を作り、label1ウィジェットのテキストをこのelapse変数を用いて記述しています。
26~30行目でupdate_time関数を作成します。中身は1秒経過するごとにelapse変数に+1し、label1ウィジェットのテキストを更新、その1秒後に再びupdate_time関数を呼び出す、というものです。
35行目でプログラム実行時にupdate_time関数を実行するよう記述しています。あとはupdate_time関数が再帰的に呼び出され続けるので、秒数のカウントが続いていきます。
after_cancelメソッドによる after の取り消し
先程のプログラムは実行時からの経過秒数をずっとカウントし続けてくれますが、再帰的処理を止める記述をしていないため、プログラムを終了しないかぎりずっと数がカウントされ続けてしまいます。
afterメソッドによる再帰的処理を止めたい場合はafter_cancelメソッドを使います。
実はafterメソッドは実行時に返り値としてIDを返しています。after_cancelメソッドはそのIDを引数に指定して実行することで、afterメソッドの実行をキャンセルすることができます。
after_cancelメソッドの構造は次のとおりです。
after_cancel(self, id)
では実際に先ほどのプログラムにafter_cancelメソッドを書き加え、10秒経過するとカウントがストップするようにしてみましょう。
import tkinter as tk # tk.Frameを継承したApplicationクラスを作成 class Application(tk.Frame): def __init__(self, master=None): super().__init__(master) # ウィンドウの設定 self.master.title("ウィンドウのタイトル") self.master.geometry("300x200") # 変数定義 self.elapse = 0 # 経過時間保存用の変数を定義 self.after_id = 0 # after_id変数を定義 # 実行内容 self.pack() self.create_widget() # create_widgetメソッドを定義 def create_widget(self): # label1ウィジェットを作成 self.label1 = tk.Label(self,text=str(self.elapse) + "秒経過しました", font="24") self.label1.pack() # 毎秒の処理 def update_time(self): self.elapse += 1 self.label1["text"] = str(self.elapse) + "秒経過しました" self.after_id = app.after(1000, self.update_time) # 10秒経過すると更新を停止 if self.elapse == 10: app.after_cancel(self.after_id) if __name__ == "__main__": root = tk.Tk() app = Application(master=root) app.update_time() app.mainloop()
14行目でIDを格納するための変数を定義し、31行目のafterメソッド実行時にIDをafter_id変数に格納します。
33~34行目でelapse変数が10の時にafter_cancelメソッドが実行され、afterメソッドによる再帰的処理を終了させています。
10秒までカウントした後、カウントが自動的にストップしました。
afterメソッドだけでは完全なリアルタイム処理はできない
前述のとおり、afterメソッドは『〇〇ミリ秒後に指定した処理を実行するという登録』ができますが、必ずしも指定した時間に処理が実行されるとは限りません。
これはafterメソッド実行時に登録された関数が自動的に実行されるのは mainloop 実行中のためです。
mainloop はアプリが暇な時に実行され続けるループ処理です。ユーザーからのマウスクリックやキーボードのキー入力など、何らかのイベントが実行されている時は mainloop は実行中ではありません。
もしもafterメソッドで指定した処理の実行タイミングで、他の処理が実行中で mainloop が止まっている場合は、次に mainloop が実行されるまで処理の実行が遅れます。
つまりafterメソッドだけでは完全なリアルタイム処理を作ることはできません。
完全なリアルタイム処理を実現するにはpythonのtimeモジュールを組み合わせて、定期的にずれた時間を合わせる処理を組み込む必要があります。