Глава 16 · Создаём классные приложения с Tkinter
Архитектура приложения: логика отдельно от виджетов
Лучшая GUI-логика — та, которую можно протестировать, даже не открывая окно.
Чистая логика отдельно от виджетов
Прямая связь с главой 13: доменную функцию можно написать и протестировать независимо от интерфейса — просто вход и выход, без единого виджета:
chistaya_logika.py
def calculate_tip(amount, percent, people):
total_tip = amount * percent / 100
return total_tip / people
print(calculate_tip(1000, 15, 2)) # 75.0
callback (обработчик кнопки)
↓
прочитать значения виджетов
↓
проверить (раздел 16.23)
чистая функция — без единого виджета внутри
↓calculate_tip(...)
↓
показать результат в виджете
Callback не должен разрастаться
Если обработчик кнопки занимает сотню строк с вложенными вычислениями — вероятно, часть этой логики стоит вынести в отдельную функцию, которую можно понять и проверить без запуска окна.
Чуть глубже: ловушка позднего связывания в lambda
Debug Lab 4: Все кнопки в цикле «запоминают» одно и то же значение
pozdnee_svyazyvanie.py
for i in range(3):
ttk.Button(root, text=str(i), command=lambda: print(i)).pack()
# Все три кнопки выведут одно и то же число — последнее значение i (2),
# а не 0, 1, 2 соответственно.
lambda: print(i) обращается к переменной i из окружающей области видимости в момент клика, а не в момент создания кнопки (глава 13, замыкания). К моменту клика цикл уже закончился, и i равно своему последнему значению — 2 — для всех трёх кнопок сразу.
Исправленный код
pozdnee_svyazyvanie_fixed.py
for i in range(3):
ttk.Button(root, text=str(i), command=lambda i=i: print(i)).pack()
# i=i создаёт значение по умолчанию, зафиксированное ИМЕННО в момент создания lambda
proverka_lambda_fix.py
callbacks = []
for i in range(3):
callbacks.append(lambda i=i: i)
values = [cb() for cb in callbacks]
print(values) # [0, 1, 2]
Практика: чистая логика и ловушка lambda
Проверяется без tkinter — на функциях и замыканиях