オフトピかもしれませんが、再帰とupdate() について
再帰呼び出しでのスタックの推移
## 再帰呼び出しでの例
import inspect
def fact(n):
depth = len(inspect.stack())
print(f"fact({n}) stack-depth: {depth}")
if n > 0:
return n + fact(n - 1)
else:
return 0
print(fact(5))
txt
1fact(5) stack-depth: 2
2fact(4) stack-depth: 3
3fact(3) stack-depth: 4
4fact(2) stack-depth: 5
5fact(1) stack-depth: 6
6fact(0) stack-depth: 7
715
8
after() で一定時間毎に呼び出す
python
1import inspect
2import tkinter as tk
3
4root = tk.Tk()
5root.withdraw() # window は非表示
6
7def tick():
8 depth = len(inspect.stack())
9 print(f"tick() stack-depth: {depth}")
10 root.after(1000, tick)
11
12root.after_idle(tick)
13root.after(3*1000, root.quit) # 3秒後に終了
14root.mainloop()
再帰呼び出しではないので、スタックの深さは一定です。
txt
1tick() stack-depth: 5
2tick() stack-depth: 5
3tick() stack-depth: 5
イベントループ内での update()
python
1"""
2update() が再帰呼び出しになってしまう例。
3
4このコードは、問題を検証するだけのコードですが、
5実際のケースでは、イベントループ内で時間のかかる処理中に、
6update する場合に、発生する可能性があります。
7"""
8
9import inspect
10import tkinter as tk
11
12root = tk.Tk()
13root.withdraw()
14
15def tick(num):
16 depth = len(inspect.stack())
17 print(f"tick({num}) start, stack-depth: {depth}")
18
19 if num > 0:
20 root.after_idle(tick, num-1)
21
22 ## コメントを外して update() の有無での挙動を確認
23 # root.update()
24
25 print(f"tick({num}) end")
26
27
28root.after_idle(tick, 3)
29root.after(3*1000, root.quit)
30root.mainloop()
updateがない場合
txt
1tick(3) start, stack-depth: 5
2tick(3) end
3tick(2) start, stack-depth: 5
4tick(2) end
5tick(1) start, stack-depth: 5
6tick(1) end
7tick(0) start, stack-depth: 5
8tick(0) end
updateが有る場合
イベントループの再帰呼び出しが発生しスタックが貯まります
txt
1tick(3) start, stack-depth: 13
2tick(2) start, stack-depth: 17
3tick(1) start, stack-depth: 21
4tick(0) start, stack-depth: 25
5tick(0) end
6tick(1) end
7tick(2) end
8tick(3) end
実際のコードでの良くない update()
こちらは逆に、コードの記述は再帰っぽくなってないが、
実際には再帰呼び出しが発生する(可能性がある)コードです。
python
1
2import inspect
3import time
4import tkinter as tk
5from tkinter import ttk
6
7MAXNUM = 10
8
9root = tk.Tk()
10var = tk.IntVar()
11progbar = ttk.Progressbar(root,
12 mode="determinate",
13 orient=tk.HORIZONTAL,
14 variable=var,
15 maximum=MAXNUM)
16progbar.pack()
17
18
19def process(count):
20 # XXX: この関数の実装は「あまり良くない」例での
21 # 具体的な問題点を示すものです。
22 depth = len(inspect.stack())
23 print(f"process({count}) start, stack: {depth}")
24
25 for num in range(1, 1+MAXNUM):
26 var.set(num)
27 root.update() # XXX: ボタンが連打された時、イベントループが再帰する可能性 ⇛ イベントの割り込みが発生
28 time.sleep(0.5) # XXX: スリープ中は操作を受け付けない
29
30 print(f"process({count}) end")
31
32count = 0
33def start():
34 # ボタンクリックの回数をカウント
35 global count
36 count += 1
37 process(count)
38
39button = ttk.Button(root, text="start", command=start)
40button.pack()
41
42root.mainloop()
43
ボタンを複数回クリックすると問題の挙動が確認できます。
update() 内でイベントが処置される(イベントループのネスト)為、
最初のイベントが完了しないまま他のイベントの処理を開始し、
他のイベントが全て終わった後で最初のイベントの残りを再開します。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。