Метод after в Tkinter ‒ отложенный вызов функции
В прошлом уроке мы использовали новый для нас метод after
, который через указанное в первом аргументе количество миллисекунд выполняет функцию, переданную во втором аргументе. В случае необходимости для этой функции в третьем и последующем аргументах метода after
передаются аргументы для ее параметров. Рассмотрим пример:
from tkinter import * def make_bigger(*widgets): for i in widgets: w = i['width'] i.config(width=int(w*1.5)) root = Tk() bt = Button(text='Hello', width=10) lbl = Label(text='World', bg='pink', width=20) root.after(2500, make_bigger, bt, lbl) bt.pack() lbl.pack() root.mainloop()
Здесь через 2,5 секунды кнопка и метка поменяют свой размер. То, что при передаче аргументов в функцию make_bigger они там упаковываются в кортеж widgets, является частным случаем. Параметры функции могут быть обычными позиционными.
Метод after
есть не только у окна, но и у других виджетов. Однако вызывать его на них редко имеет смысл. В примере ниже не важно, вызываем ли мы after
на метку или окно.
from tkinter import * ph2 = ('Big ball', 'Small roll', 'Round table', 'Hot cup') root = Tk() lbl = Label(bg='pink', width=20) for i in range(len(ph2)): lbl.after(1000, lambda: lbl.config(text=ph2[i])) lbl.pack() root.mainloop()
Однако проблема этой программы в другом. Она не работает так, как надо. Мы ожидаем, что надпись в метке будет меняться через каждую секунду. На самом деле через 1 секунду отобразится последняя фраза: "Hot cup".
Если метод after
откладывает вызов функции на указанное время, значит, она не начинает исполняться, когда вызывается after
. Ее код стартует только через указанное время.
Проследим, что происходит в программе выше. Цикл for
очень быстро прокручивает все свои итерации, все четыре вызова after
происходят почти одномоментно. Через 1 секунду интерпретатор уже давно забыл про цикл for
. Однако начинают исполняться отложенные вызовы функции. Поскольку все они идут сразу друг за другом, мы видим в метке только последнюю фразу.
Другими словами, 1 секунда ‒ это не задержка между вызовами функции. Это время отложения вызова функции, переданной в after
. И у всех вызовов в нашем случае оно одно и то же.
Чтобы убедиться, что пользовательская функция вызывается четыре раза и с разными аргументами, вместо lambda
создадим обычную и в ней дополнительно будем выводить фразу на экран:
def change_ph(widget, phrase): widget['text'] = phrase print(phrase) ... for i in range(len(ph2)): lbl.after(1000, change_ph, lbl, ph2[i])
И хотя в метке будет только последняя фраза, на экране вы увидите все четыре. Они появятся все сразу через 1 секунду работы программы.
Таким образом, решение проблемы ‒ это вызовы after
через разные промежутки времени:
def change_ph(widget, phrase): widget['text'] = phrase ... for i in range(len(ph2)): lbl.after((i+1)*1000, change_ph, lbl, ph2[i])
При этом если заменить обычную функцию на lambda: lbl.config(text=ph2[i])
, вернемся к варианту неверной работы программы. Видимо это происходит потому, что нет аргумента, и для всех вызовов значение i берется в его конечном варианте. Поэтому рабочий код лямбды будет выглядеть так: lambda j=i: lbl.config(text=ph2[j])
.
Чаще метод after
вкладывают не в цикл, а вызывают внутри функции для отложенного вызова самой этой функции. Получается своего рода рекурсия.
from tkinter import * ph2 = ('Big ball', 'Small roll', 'Round table', 'Hot cup') def change_ph(widget, kit, n): widget['text'] = kit[len(kit) - n] n = n - 1 if n > 0: lbl.after(1000, change_ph, widget, kit, n) root = Tk() lbl = Label(bg='pink', width=20) change_ph(lbl, ph2, len(ph2)) lbl.pack() root.mainloop()
В данном случае нет надобности вызывать after
через разные промежутки времени, как было с циклом. Здесь каждый следующий вызов change_ph происходит через секунду после предыдущего. В свою очередь и метод after
вызывается через 1 секунду после своего предыдущего вызова. Получается следующая последовательность вызовов: change_ph()
→ after()
→ 1 сек. → change_ph()
→ after()
→ 1 сек. → ….
Практическая работа
Напишите программу, в которой в метке через каждые 2 секунды появляется новое случайное число. После того как программа сгенерирует и отобразит пять случайных чисел, они должны быть выведены в метке все вместе единым списком.
Курс с примерами решений практических работ: pdf-версия