Вспомните: в уроке 2.1 программа спрашивала сумму через input() и в цикле с try/except переспрашивала, пока человек не введёт число. Работает, но коллеге, который не открывает терминал, такую программу не отдашь. В этом уроке у итогов расходов появится окно: поле для суммы, кнопка «Добавить» и надпись «Итого», а ошибка ввода будет всплывать отдельным окошком.
🎯 После урока сможете: создать окно через tk.Tk(), разместить в нём надпись, поле ввода и кнопку, прочитать и очистить поле, поменять текст надписи, привязать к кнопке свою функцию через command, разложить виджеты по сетке grid, показать ошибку через messagebox и проверить обработчик кнопки без окна — вызвав его прямо из кода.
Первое окно: Tk, title, geometry, mainloop
Tkinter входит в поставку Python с сайта python.org для Windows и macOS: устанавливать ничего не нужно (если Python на Mac поставлен через Homebrew, Tkinter добавляют отдельно: brew install python-tk). Модуль принято импортировать под коротким именем tk. Окно программы — это объект tk.Tk(), у него есть заголовок и размер.
import tkinter as tk
root = tk.Tk()
root.title("Калькулятор расходов")
root.geometry("320x160")
root.mainloop()
root = tk.Tk()— создаёт главное окно; имяroot(корень) — общепринятое;root.title("…")— текст в заголовке окна;root.geometry("320x160")— размер в пикселях: ширина, латинская буква x, высота;root.mainloop()— запускает цикл ожидания: окно висит на экране и реагирует на мышь и клавиатуру, пока его не закроют. Строки послеmainloop()выполнятся только после закрытия окна.

Сохраните код в файл
okno.pyи запустите его как обычно:python okno.py. Появится пустое окно. Закройте его крестиком — программа завершится. На Linux, если Tkinter не найден, поставьте пакетpython3-tkменеджером пакетов.
Проверьте себя
Контрольный вопрос. Какая строка держит окно на экране и заставляет его отвечать на нажатия, пока человек его не закроет?
Подсказка: Вспомните, что происходит со строками кода, которые стоят после этой: когда они выполнятся?
Онлайн-проверка ответа появится позже
Задание. Какой высоты будет окно в пикселях? Введите число.
Исходный код для этого задания:
import tkinter as tk root = tk.Tk() root.title("Поиск в журнале") root.geometry("480x220") root.mainloop()Подсказка: В строке размера два числа через букву x. Какое из них идёт первым — ширина или высота?
✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.
Онлайн-проверка ответа появится позже
Надпись, поле и кнопка: Label, Entry, Button
Всё, что лежит в окне, называется виджетами. Для калькулятора нужны три:
tk.Label— надпись: текст, который человек читает, но не редактирует;tk.Entry— поле ввода в одну строку;tk.Button— кнопка.
Первым аргументом виджету передают окно, в котором он живёт (root), дальше — параметры: text — текст, width — ширина (у поля — в символах). Создать виджет мало — его нужно ещё разместить в окне. Самый простой способ — pack(): виджеты встают друг под другом в порядке вызова. Параметр pady добавляет отступ сверху и снизу в пикселях.
import tkinter as tk
root = tk.Tk()
root.title("Калькулятор расходов")
label = tk.Label(root, text="Сумма покупки:")
label.pack(pady=5)
entry = tk.Entry(root, width=15)
entry.pack()
button = tk.Button(root, text="Добавить")
button.pack(pady=5)
root.mainloop()
Кнопка пока ничего не делает — это следующий раздел. Сначала научимся работать с полем и надписью из кода. У поля три метода:
entry.get()— вернуть текст из поля. Всегда строку, даже если там цифры;entry.insert(0, "450")— вставить текст;0— позиция, с которой вставлять (начало поля);entry.delete(0, tk.END)— удалить текст с позиции 0 до конца поля, то есть очистить его.
У надписи текст меняют методом config, а читают методом cget (от «configuration get»):
label.config(text="Итого: 450")— поставить новый текст;label.cget("text")— прочитать текущий текст.
Эти методы работают и без показа окна: mainloop() не вызываем, окно так и не появится, а значения можно напечатать. В конце вызываем root.destroy() — он уничтожает окно и все виджеты в нём. Так удобно проверять код без мыши:
import tkinter as tk
root = tk.Tk()
entry = tk.Entry(root)
label = tk.Label(root, text="Итого: 0")
entry.insert(0, "450")
print(entry.get(), type(entry.get()))
entry.delete(0, tk.END)
print("После delete:", repr(entry.get()))
label.config(text="Итого: 450")
print(label.cget("text"))
root.destroy()
Вывод:
450 <class 'str'>
После delete: ''
Итого: 450
repr() печатает строку в кавычках — так видно, что поле стало пустым, а не пропало из вывода.
Проверьте себя
Контрольный вопрос. Нужен виджет, в который человек впечатает сумму покупки. Какой выбрать?
Подсказка: Какой из перечисленных виджетов позволяет человеку печатать текст, а не только читать его?
Онлайн-проверка ответа появится позже
Задание. Что напечатает программа? Введите строку вывода целиком.
Исходный код для этого задания:
import tkinter as tk root = tk.Tk() entry = tk.Entry(root) label = tk.Label(root, text="Сумма: ?") entry.insert(0, "450") entry.delete(0, tk.END) entry.insert(0, "1200") label.config(text="Сумма: " + entry.get()) print(label.cget("text")) root.destroy()Подсказка: Проследите содержимое поля после каждой строки: что в нём остаётся после delete(0, tk.END) и что вставляется потом?
✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.
Онлайн-проверка ответа появится позже
Задание. Человек ввёл в поле 25. Что напечатает программа? Введите вывод.
Исходный код для этого задания:
import tkinter as tk root = tk.Tk() entry = tk.Entry(root) entry.insert(0, "25") print(entry.get() * 2) root.destroy()Подсказка: Какого типа значение возвращает entry.get()? Что делает умножение на 2 со значением такого типа?
✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.
Онлайн-проверка ответа появится позже
command: что делать по нажатию кнопки
Чтобы кнопка работала, пишут обычную функцию без параметров и передают её имя в параметр command. Tkinter сам вызовет функцию при каждом нажатии. Такую функцию называют обработчиком.
Итог должен жить между нажатиями, поэтому храним его в переменной вне функции. Чтобы функция могла её изменить, внутри пишут global total — «это переменная из программы, а не новая местная». Без этой строки total = total + … внутри функции даст ошибку UnboundLocalError. Если функция меняет несколько таких переменных, их перечисляют в одной строке через запятую: global total, errors.
import tkinter as tk
total = 0
def add():
global total
total = total + int(entry.get())
label.config(text=f"Итого: {total}")
entry.delete(0, tk.END)
root = tk.Tk()
root.title("Калькулятор расходов")
entry = tk.Entry(root, width=15)
entry.pack(pady=5)
button = tk.Button(root, text="Добавить", command=add)
button.pack()
label = tk.Label(root, text="Итого: 0")
label.pack(pady=5)
root.mainloop()
Функция add объявлена выше, чем создаются entry и label, но это не мешает: к моменту нажатия кнопки вся программа уже выполнена до mainloop(), и виджеты существуют.
Пишите
command=add— без скобок. Со скобкамиcommand=add()функция выполнится сразу, в момент создания кнопки, а не по нажатию. В калькуляторе поле в этот момент уже есть, но оно пустое:int("")дастValueError, и программа остановится, окно так и не появится. А если функция и не упадёт, вcommandпопадёт то, что она вернула, —None, и нажатия потом ничего делать не будут.
Проверить обработчик можно без мыши: вставить текст в поле и вызвать add() прямо из кода — ровно то же, что делает кнопка. Этот приём понадобится в заданиях и миссиях:
import tkinter as tk
total = 0
def add():
global total
total = total + int(entry.get())
label.config(text=f"Итого: {total}")
entry.delete(0, tk.END)
root = tk.Tk()
entry = tk.Entry(root)
label = tk.Label(root, text="Итого: 0")
for s in ["450", "380", "620"]:
entry.insert(0, s)
add()
print(label.cget("text"))
root.destroy()
Вывод:
Итого: 1450
Проверьте себя
Контрольный вопрос. Сразу после запуска надпись показывает «Итого: 0», хотя кнопку «Сброс» никто не нажимал, а нажатия потом ничего не меняют. В чём ошибка?
Исходный код для этого задания:
import tkinter as tk total = 900 def reset(): global total total = 0 label.config(text="Итого: 0") root = tk.Tk() label = tk.Label(root, text="Итого: 900") label.pack() button = tk.Button(root, text="Сброс", command=reset()) button.pack() root.mainloop()Подсказка: Сравните строку создания кнопки с правилом из материала: что должно стоять после command= — имя функции или её вызов?
Онлайн-проверка ответа появится позже
Задание. Человек нажал кнопку 4 раза. Что будет написано на надписи? Введите текст надписи целиком.
Исходный код для этого задания:
import tkinter as tk cups = 0 def click(): global cups cups = cups + 2 label.config(text=f"Чашек: {cups}") root = tk.Tk() label = tk.Label(root, text="Чашек: 0") label.pack() tk.Button(root, text="Выпил", command=click).pack() root.mainloop()Подсказка: Каждое нажатие — один вызов click(). На сколько меняется cups за один вызов?
✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.
Онлайн-проверка ответа появится позже
Раскладка по сетке: grid
pack() ставит виджеты столбиком. Для формы «подпись слева — поле справа» удобнее сетка grid: окно делится на строки и столбцы, у каждого виджета указывают row (номер строки) и column (номер столбца). Нумерация с нуля, как в списках.
import tkinter as tk
root = tk.Tk()
root.title("Калькулятор расходов")
tk.Label(root, text="Сумма:").grid(row=0, column=0, padx=5, pady=5)
entry = tk.Entry(root, width=12)
entry.grid(row=0, column=1, padx=5)
tk.Button(root, text="Добавить").grid(row=0, column=2, padx=5)
label = tk.Label(root, text="Итого: 0")
label.grid(row=1, column=0, columnspan=3, pady=5)
root.mainloop()
padx,pady— отступы слева-справа и сверху-снизу;columnspan=3— виджет занимает три столбца подряд: надпись «Итого» растягивается под всей первой строкой;sticky="w"— прижать виджет к стороне ячейки:"w"— влево (запад),"e"— вправо (восток).
Если виджет создан сразу в одной строке с .grid(...), как tk.Label(...).grid(...), в переменную его не сохраняют: grid() возвращает None. Для надписей, которые не меняются, так и делают. А поле и надпись «Итого» мы будем читать и менять — их сначала кладём в переменную, потом размещаем.
В одном окне выберите что-то одно:
packилиgrid. Если часть виджетов окна разместить черезpack, а часть — черезgrid, Tkinter остановит программу с ошибкойTclError.

Проверьте себя
Контрольный вопрос. Где окажется кнопка «Найти»?
Исходный код для этого задания:
import tkinter as tk root = tk.Tk() tk.Label(root, text="Слово:").grid(row=0, column=0) entry = tk.Entry(root) entry.grid(row=0, column=1) tk.Button(root, text="Найти").grid(row=1, column=1) root.mainloop()Подсказка: Сравните row и column кнопки с row и column поля: что совпадает, а что нет? Строки идут сверху вниз.
Онлайн-проверка ответа появится позже
Контрольный вопрос. Программа при запуске остановилась с ошибкой TclError. Почему?
Исходный код для этого задания:
import tkinter as tk root = tk.Tk() tk.Label(root, text="Сумма:").grid(row=0, column=0) entry = tk.Entry(root) entry.grid(row=0, column=1) tk.Button(root, text="Добавить").pack() root.mainloop()Подсказка: Посмотрите, каким способом размещён каждый виджет этого окна, и сверьтесь с предупреждением в материале.
Онлайн-проверка ответа появится позже
messagebox: сообщить об ошибке отдельным окошком
Ввод в окне ничем не лучше ввода в терминале: человек напишет «сто» или «12,5». Ловим это тем же try/except, что в уроке 2.1, но сообщение показываем не print, а всплывающим окном. Модуль messagebox нужно импортировать отдельно: from tkinter import messagebox.
messagebox.showinfo("Заголовок", "Текст")— сообщение со значком «i»;messagebox.showwarning(…)— предупреждение, жёлтый значок;messagebox.showerror(…)— ошибка, красный значок;messagebox.askyesno("Заголовок", "Вопрос?")— вопрос с кнопками «Да» и «Нет»; возвращаетTrue, если нажали «Да», иFalse, если «Нет».
Первый аргумент — заголовок окошка, второй — текст. Пока окошко открыто, программа ждёт: следующая строка обработчика выполнится только после нажатия OK.
from tkinter import messagebox
def add():
global total
try:
amount = float(entry.get().replace(",", "."))
if amount <= 0:
raise ValueError("сумма должна быть больше нуля")
except ValueError:
messagebox.showerror("Ошибка ввода", "Введите сумму числом больше нуля, например 450 или 99,90")
else:
total = total + amount
label.config(text=f"Итого: {total:.2f}")
entry.delete(0, tk.END)
Здесь работают приёмы урока 2.1: запятая меняется на точку, float даёт ValueError на «сто», а свой raise отбраковывает ноль и минус. Итог обновляется только в else — при удачном разборе. Запись {total:.2f} в f-строке печатает число с двумя знаками после точки: 450.0 превратится в 450.00. Поле очищается в любом случае, чтобы человек ввёл заново.

Проверьте себя
Контрольный вопрос. Человек ввёл в поле даты «31.02.2026». Какой вызов покажет окно ошибки с красным значком?
Подсказка: Вспомните список функций messagebox: у какой из них значок ошибки?
Онлайн-проверка ответа появится позже
Задание. При вызове clear() откроется вопрос, и человек нажмёт «Нет». Что напечатает программа? Введите строку вывода целиком.
Исходный код для этого задания:
import tkinter as tk from tkinter import messagebox total = 3700 def clear(): global total if messagebox.askyesno("Сброс", "Обнулить итог?"): total = 0 label.config(text=f"Итого: {total}") root = tk.Tk() label = tk.Label(root, text="Итого: 3700") clear() print(label.cget("text")) root.destroy()Подсказка: Что возвращает askyesno при нажатии «Нет»? Выполнится ли тогда строка внутри if?
✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.
Онлайн-проверка ответа появится позже
Собираем калькулятор расходов
Соберём всё вместе: сетка из урока, обработчик с проверкой ввода и кнопка «Сбросить» с вопросом. Это полная программа — её можно запускать.
import tkinter as tk
from tkinter import messagebox
total = 0
def add():
global total
try:
amount = float(entry.get().replace(",", "."))
if amount <= 0:
raise ValueError("сумма должна быть больше нуля")
except ValueError:
messagebox.showerror("Ошибка ввода", "Введите сумму числом больше нуля, например 450 или 99,90")
else:
total = total + amount
label.config(text=f"Итого: {total:.2f}")
entry.delete(0, tk.END)
def reset():
global total
if messagebox.askyesno("Сброс", "Обнулить итог?"):
total = 0
label.config(text="Итого: 0.00")
root = tk.Tk()
root.title("Калькулятор расходов")
tk.Label(root, text="Сумма:").grid(row=0, column=0, padx=5, pady=5)
entry = tk.Entry(root, width=12)
entry.grid(row=0, column=1, padx=5)
tk.Button(root, text="Добавить", command=add).grid(row=0, column=2, padx=5)
label = tk.Label(root, text="Итого: 0.00")
label.grid(row=1, column=0, columnspan=2, pady=5)
tk.Button(root, text="Сбросить", command=reset).grid(row=1, column=2, padx=5)
root.mainloop()

Запустите программу у себя, введите несколько сумм, потом «сто», «-20» и «12,5». Посмотрите, какие попали в итог, а какие вызвали окно ошибки. Нажмите «Сбросить» и ответьте «Нет», затем «Да».
Проверьте себя
Задание. Окна ошибок в этой программе при проверке просто закрываются кнопкой OK. Что она напечатает? Введите два числа через пробел, как в выводе.
Исходный код для этого задания:
import tkinter as tk from tkinter import messagebox total = 0 errors = 0 def add(): global total, errors try: amount = float(entry.get().replace(",", ".")) if amount <= 0: raise ValueError("сумма должна быть больше нуля") except ValueError: errors = errors + 1 messagebox.showerror("Ошибка", "Нужна сумма больше нуля") else: total = total + amount entry.delete(0, tk.END) root = tk.Tk() entry = tk.Entry(root) for s in ["250", "двести", "99,5", "0", "", "150,5"]: entry.insert(0, s) add() print(total, errors) root.destroy()Подсказка: Для каждой строки списка решите: превращается ли она в число после замены запятой, проходит ли проверку amount <= 0, в какую ветку уходит — else или except.
✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.
Онлайн-проверка ответа появится позже
Задание. В этом обработчике после ошибки стоит return, поэтому поле при ошибке не очищается, а insert(0, …) дописывает новый текст в начало того, что там осталось. Что напечатает программа первой строкой? Введите строку целиком.
Исходный код для этого задания:
import tkinter as tk from tkinter import messagebox total = 0 def add(): global total try: amount = int(entry.get()) except ValueError: messagebox.showerror("Ошибка", "Введите целое число") return total = total + amount label.config(text=f"Итого: {total}") entry.delete(0, tk.END) root = tk.Tk() entry = tk.Entry(root) label = tk.Label(root, text="Итого: 0") for s in ["300", "12,5", "200"]: entry.insert(0, s) add() print(label.cget("text")) print(repr(entry.get())) root.destroy()Подсказка: Проследите содержимое поля после каждого вызова add(): что остаётся в нём после ошибки и что получится, когда в начало вставят следующую строку?
✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.
Онлайн-проверка ответа появится позже
Задание. Соберите сами окно «Учёт чтения»: поле для числа прочитанных минут, кнопка «Добавить» и надпись «Всего минут: …». Обработчик превращает текст поля в int, отбраковывает через raise ValueError число ≤ 0, при ошибке показывает messagebox.showwarning, при успехе прибавляет минуты к итогу и меняет надпись; поле очищается в любом случае. Проверьте обработчик без мыши: в конце программы вместо mainloop() по очереди вставляйте в поле строки из списка ниже и вызывайте обработчик. В поле ответа — число, которое окажется на надписи.
["5", "10", "", "пять", "15", "-5", "20", "0", "7", "десять", "13", "30"]Подсказка: Возьмите за основу калькулятор расходов из урока: те же три виджета, global для итога, try/except/else. Проверка — цикл for со вставкой в поле и вызовом обработчика, как в разделе про command.
✅ Готово, если: ответ раскрывает то, что просит критерий приёмки в задании выше.
Онлайн-проверка ответа появится позже
Задание. Проектная миссия «Калькулятор расходов для коллеги». Сделайте окно: подпись «Сумма:», поле, кнопка «Добавить», надпись «Итого: …» с двумя знаками после точки и кнопка «Сбросить» с вопросом askyesno. Обработчик принимает дробную часть через запятую, отбраковывает через raise ValueError сумму ≤ 0, на плохом вводе показывает messagebox.showerror и считает ошибки. Ниже — 51 строка, которую коллега ввёл в поле по очереди за месяц (каждая строка — одно содержимое поля; пустая строка — нажал «Добавить» с пустым полем). Для проверки добавьте переменную SHOW_ERRORS: при False окно ошибки не показывается, только считается, — и прогоните строки через поле и обработчик циклом, как в уроке. В поле ответа — итог, округлённый round() до целого. Приложите файл программы .py; в поле ответа — число.
Принято, если:
1. введённое число совпадает с эталоном;
2. приложен файл .py, который это число выдаёт на данных из условия;Самопроверка перед сдачей (преподаватель смотрит на занятии):
3. раскладка по grid, pack в том же окне не используется;
4. в command передано имя функции без скобок;
5. при SHOW_ERRORS = True на «сто» всплывает окно ошибки, итог не меняется.357
632
1014
2990,51
2124
273
1960
43
сто
3241,11
2028
3492,46
866
1408,77
-150
498
1837,16
196
968,49
3023
0
726
3368
2368
377
2444,46
12р
1389,94
1681
1225
325
77
двести
1864,21
295,43
237
636
832
-5,5
2045,77
2860
1516
1062
2191
1 200
995,58
2418
839
2128,97
3406
Подсказка: Начните с калькулятора из урока и добавьте счётчик ошибок в ветку except. Строки данных удобно вставить в программу списком и пройти циклом for: insert в поле, вызов обработчика.
✅ Готово, если: ответ раскрывает то, что просит критерий приёмки в задании выше.
Онлайн-проверка ответа появится позже
Задание. Проектная миссия «Расходы по категориям». Сделайте окно на grid: в первой строке подпись «Категория:» и поле, во второй — «Сумма:» и поле, в третьей — кнопка «Добавить», в четвёртой — надпись с итогом той категории, в которую только что добавили (например «кафе: 1840»). Итоги храните в словаре «категория → сумма». Пустая категория, нечисловая сумма и сумма ≤ 0 — ValueError (свой raise для пустой категории и для ≤ 0), messagebox.showwarning, запись не учитывается. Оба поля очищаются после каждого нажатия. Ниже — 48 записей «категория;сумма», которые ввели в окно по очереди (слева от «;» — что было в поле категории, справа — в поле суммы). Прогоните их через поля и обработчик циклом, окна предупреждений для прогона отключите переменной, как в первой миссии. В поле ответа — итог категории «продукты». Приложите файл программы .py; в поле ответа — число.
Принято, если:
1. введённое число совпадает с эталоном;
2. приложен файл .py, который это число выдаёт на данных из условия;Самопроверка перед сдачей (преподаватель смотрит на занятии):
3. итоги хранятся в словаре, а не в отдельной переменной на каждую категорию;
4. подписи и поля выровнены по сетке, надпись итога меняется через config.кафе;624
транспорт;64
;172
продукты;64
транспорт;1353
связь;0
связь;401
кафе;1837
транспорт;1557
;398
транспорт;591
кафе;2036
кафе;1605
продукты;202
транспорт;сто
транспорт;2230
;1605
транспорт;528
связь;1363
транспорт;699
транспорт;1871
продукты;1905
продукты;82
;сто
продукты;588
кафе;404
продукты;2119
транспорт;1445
продукты;1309
транспорт;1010
;472
транспорт;139
продукты;-300
кафе;2139
кафе;819
продукты;1555
продукты;150
;788
продукты;417
транспорт;1603
связь;277
транспорт;-300
транспорт;867
кафе;421
;958
продукты;1378
связь;2327
кафе;691
Подсказка: Разбирайте запись через split(«;») уже в цикле проверки, а обработчик пусть читает только поля — как кнопка в настоящем окне. Для словаря пригодится get(категория, 0).
✅ Готово, если: ответ раскрывает то, что просит критерий приёмки в задании выше.
Онлайн-проверка ответа появится позже
Что дальше
Теперь вы умеете создавать окно через tk.Tk(), класть в него надписи, поля и кнопки, раскладывать их по сетке grid, читать и очищать поле, менять текст надписи, привязывать к кнопке функцию через command, сообщать об ошибке ввода через messagebox и проверять обработчик без мыши. В следующем уроке окно станет настоящей утилитой: научим его выбирать файл через диалог, показывать список, ставить галочки и запоминать настройки в JSON между запусками.
← Назад · ↑ В начало урока · ⌂ К навигатору курса · Вперёд →
