Учёт расходов на Python с Tkinter
Важно: кодовые блоки в статье сохраняют исходный пример без локализации строк в коде — это облегчает запуск «как есть». В тексте ниже объяснены все ключевые шаги, варианты улучшений и сценарии тестирования.
О чём статья
Эта статья переводит и поясняет пример приложения Expense Tracker (трекер расходов) на Python. Вы узнаете:
- какие модули требуются и зачем они нужны;
- структуру приложения и назначение основных методов;
- как работает добавление/редактирование/удаление записей и экспорт в CSV;
- как строится диаграмма расходов по категориям;
- варианты улучшения, тесты, критерии приёмки и рекомендации по безопасности и локализации.
Базовые модули: Tkinter, CSV и Matplotlib
Кратко о модулях:
- Tkinter — стандартный модуль для создания настольных GUI-приложений в Python. Прост в освоении, подходит для лёгких инструментов.
- CSV — встроенная библиотека Python для чтения/записи CSV-файлов.
- Matplotlib — библиотека для построения графиков и диаграмм (используется для круговой диаграммы расходов).
Установка зависимостей (если нужно):
pip install tk matplotlib Примечание: на некоторых системах (Linux) модуль tk может устанавливаться через пакетный менеджер системы (apt, dnf и т. п.).
Быстрая карта архитектуры приложения
Классическое одностороннее приложение GUI:
- UI (Tkinter) — элементы ввода, список, кнопки;
- Модель (в примере — список Python) — хранение записей в памяти;
- Хранилище — CSV-файл для экспорта/импорта (можно заменить на SQLite);
- Визуализация — Matplotlib для диаграммы.
В примере все бизнес-данные хранятся в памяти в формате списка кортежей (amount, item, category, date).
Исходный код: структура и ключевые фрагменты
Ниже сохранены оригинальные блоки кода из примера. Их можно вставить в файл и запускать, как есть.
`import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import csv
import matplotlib.pyplot as plt
class ExpenseTrackerApp(tk.Tk):
def __init__(self):
super().__init__()
self.title("Expense Tracker")
self.geometry("1300x600")
self.expenses = []
self.categories = [
"Food",
"Transportation",
"Utilities",
"Entertainment",
"Other",
]
self.category_var = tk.StringVar(self)
self.category_var.set(self.categories[0])
self.create_widgets()`
Далее — метод create_widgets, формирующий элементы интерфейса:
` def create_widgets(self):
self.label = tk.Label(
self, text="Expense Tracker", font=("Helvetica", 20, "bold")
)
self.label.pack(pady=10)
self.frame_input = tk.Frame(self)
self.frame_input.pack(pady=10)
self.expense_label = tk.Label(
self.frame_input, text="Expense Amount:", font=("Helvetica", 12)
)
self.expense_label.grid(row=0, column=0, padx=5)
self.expense_entry = tk.Entry(
self.frame_input, font=("Helvetica", 12), width=15
)
self.expense_entry.grid(row=0, column=1, padx=5)
self.item_label = tk.Label(
self.frame_input, text="Item Description:", font=("Helvetica", 12)
)
self.item_label.grid(row=0, column=2, padx=5)
self.item_entry = tk.Entry(self.frame_input, font=("Helvetica", 12), width=20)
self.item_entry.grid(row=0, column=3, padx=5)
self.category_label = tk.Label(
self.frame_input, text="Category:", font=("Helvetica", 12)
)
self.category_label.grid(row=0, column=4, padx=5)
self.category_dropdown = ttk.Combobox(
self.frame_input,
textvariable=self.category_var,
values=self.categories,
font=("Helvetica", 12),
width=15,
)
self.category_dropdown.grid(row=0, column=5, padx=5)
self.date_label = tk.Label(
self.frame_input, text="Date (YYYY-MM-DD):", font=("Helvetica", 12)
)
self.date_label.grid(row=0, column=6, padx=5)
self.date_entry = tk.Entry(self.frame_input, font=("Helvetica", 12), width=15)
self.date_entry.grid(row=0, column=7, padx=5)`
Элементы управления списком и кнопки:
` self.add_button = tk.Button(self, text="Add Expense", command=self.add_expense)
self.add_button.pack(pady=5)
self.frame_list = tk.Frame(self)
self.frame_list.pack(pady=10)
self.scrollbar = tk.Scrollbar(self.frame_list)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.expense_listbox = tk.Listbox(
self.frame_list,
font=("Helvetica", 12),
width=70,
yscrollcommand=self.scrollbar.set,
)
self.expense_listbox.pack(pady=5)
self.scrollbar.config(command=self.expense_listbox.yview)
self.edit_button = tk.Button(
self, text="Edit Expense", command=self.edit_expense
)
self.edit_button.pack(pady=5)
self.delete_button = tk.Button(
self, text="Delete Expense", command=self.delete_expense
)
self.delete_button.pack(pady=5)
self.save_button = tk.Button(
self, text="Save Expenses", command=self.save_expenses
)
self.save_button.pack(pady=5)
self.total_label = tk.Label(
self, text="Total Expenses:", font=("Helvetica", 12)
)
self.total_label.pack(pady=5)
self.show_chart_button = tk.Button(
self, text="Show Expenses Chart", command=self.show_expenses_chart
)
self.show_chart_button.pack(pady=5)
self.update_total_label()`
Методы для добавления, редактирования и удаления записей:
` def add_expense(self):
expense = self.expense_entry.get()
item = self.item_entry.get()
category = self.category_var.get()
date = self.date_entry.get()
if expense and date:
self.expenses.append((expense, item, category, date))
self.expense_listbox.insert(
tk.END, f"{expense} - {item} - {category} ({date})"
)
self.expense_entry.delete(0, tk.END)
self.item_entry.delete(0, tk.END)
self.date_entry.delete(0, tk.END)
else:
messagebox.showwarning("Warning", "Expense and Date cannot be empty.")
self.update_total_label()`
Редактирование записи:
` def edit_expense(self):
selected_index = self.expense_listbox.curselection()
if selected_index:
selected_index = selected_index[0]
selected_expense = self.expenses[selected_index]
new_expense = simpledialog.askstring(
"Edit Expense", "Enter new expense:", initialvalue=selected_expense[0]
)
if new_expense:
self.expenses[selected_index] = (
new_expense,
selected_expense[1],
selected_expense[2],
selected_expense[3],
)
self.refresh_list()
self.update_total_label()`
Удаление записи и обновление списка:
` def delete_expense(self):
selected_index = self.expense_listbox.curselection()
if selected_index:
selected_index = selected_index[0]
del self.expenses[selected_index]
self.expense_listbox.delete(selected_index)
self.update_total_label()`
Обновление списка и сохранение в CSV:
` def refresh_list(self):
self.expense_listbox.delete(0, tk.END)
for expense, item, category, date in self.expenses:
self.expense_listbox.insert(
tk.END, f"{expense} - {item} - {category} ({date})"
)`
` def update_total_label(self):
total_expenses = sum(float(expense[0]) for expense in self.expenses)
self.total_label.config(text=f"Total Expenses: USD {total_expenses:.2f}")
def save_expenses(self):
with open("expenses.csv", "w", newline="") as csvfile:
writer = csv.writer(csvfile)
column_headers = ["Expense Amount", "Item Description", "Category", "Date"]
writer.writerow(column_headers)
for expense in self.expenses:
writer.writerow(expense))`
Построение диаграммы расходов по категориям:
` def show_expenses_chart(self):
category_totals = {}
for expense, _, category, _ in self.expenses:
try:
amount = float(expense)
except ValueError:
continue
category_totals[category] = category_totals.get(category, 0) + amount`
И финальная часть визуализации и запуск приложения:
` categories = list(category_totals.keys())
expenses = list(category_totals.values())
plt.figure(figsize=(8, 6))
plt.pie(
expenses, labels=categories, autopct="%1.1f%%", startangle=140, shadow=True
)
plt.axis("equal")
plt.title(f"Expense Categories Distribution (USD)")
plt.show()`
`if __name__ == "__main__":
app = ExpenseTrackerApp()
app.mainloop()`
Как работать с приложением (пошагово)
- Запустите скрипт. Откроется окно с полями: сумма, описание, категория, дата.
- Введите сумму (например, 12.50), описание и дату в формате ГГГГ-ММ-ДД, нажмите Add Expense.
- Запись появится в списке; можно выбрать её и нажать Edit Expense или Delete Expense.
- Нажмите Save Expenses, чтобы экспортировать все записи в файл expenses.csv.
- Нажмите Show Expenses Chart — откроется круговая диаграмма распределения расходов по категориям.
Изображения в примере (показываются в статье):

Локализация интерфейса и форматов
В текущем примере строки интерфейса в коде на английском. Для локализации рекомендуем:
- вынести все текстовые метки в словарь ресурсов (например, dict с ключами ‘add’, ‘delete’ и т. д.);
- форматировать валюту согласно локали (модуль locale или Babel);
- представление даты — использовать datetime и формат по локали (например, DD.MM.YYYY для РФ);
- перевод категорий и возможность их редактирования пользователем.
Пример правила: храните данные в нейтральном формате (ISO 8601 для дат, число для сумм), а при отображении форматируйте по локали.
Когда такой подход сработает и когда — нет (ограничения)
Когда хорош:
- нужен быстрый рабочий прототип или полезный утилитарный инструмент;
- небольшое количество данных и однопользовательские сценарии;
- важна простота и отсутствие зависимости от сервера.
Когда не подходит:
- многопользовательская система с одновременным доступом;
- большие объёмы данных (миллионы записей) — CSV неэффективен;
- требования к аудиту, шифрованию и резервированию данных — нужна СУБД и безопасность на уровне приложения.
Альтернативные подходы и расширения
- Хранение: заменить CSV на SQLite (sqlite3) — небольшая встраиваемая СУБД, позволит фильтры и индексирование.
- UI: использовать Kivy (кроссплатформенный, мобильные устройства) или PyQt/PySide (более богатый набор виджетов).
- Веб-версия: Flask/FastAPI + React/Vue — удобно для удалённого доступа и синхронизации между устройствами.
- Аналитика: подключить pandas для агрегаций и экспорта в Excel.
Выбор зависит от требований к масштабируемости, совместному использованию и доступности с устройств.
Мини‑методология разработки (шаги)
- Сформулировать минимально рабочий набор функций (MVP): добавление, список, экспорт, диаграмма.
- Написать UI с валидацией ввода (числа, даты).
- Добавить сохранение/загрузку (CSV → SQLite при необходимости).
- Написать тесты для критических функций (включая проверки парсинга чисел и дат).
- Добавить локализацию и настройку валюты.
- Провести обзор безопасности и приватности.
Роль‑ориентированные чеклисты
Разработчик:
- реализовать валидацию суммы и даты;
- обработать нечисловые значения и исключения при чтении/записи файлов;
- написать модульные тесты для функций подсчёта и агрегации.
Тестировщик:
- сценарии добавления/редактирования/удаления;
- проверка корректности сумм (плавающая точка, округление);
- проверка экспорта/импорта CSV с неанглийскими символами.
Продуктовый менеджер/пользователь:
- можно ли настроить категории?;
- есть ли фильтры и поиск?;
- корректно ли работает локализация дат и валюты?
Критерии приёмки
- Приложение запускается без ошибок в стандартной среде Python с установленными зависимостями.
- Добавление записи появляет её в списке и увеличивает итоговую сумму.
- Редактирование меняет сумму и отображение; удаление удаляет запись и корректно обновляет итог.
- Экспорт создаёт корректный CSV, пригодный для импорта в Excel.
- Диаграмма отображает распределение по категориям без падения приложения.
Тестовые случаи (примеры)
- Добавить запись с суммой 0 — приложение должно принять (или отклонить по требованию).
- Добавить нечисловую сумму — ожидать предупреждение или игнорирование записи.
- Добавить запись с датой в неверном формате — ожидать предупреждение.
- Сохранить и перезагрузить CSV, проверить соответствие записей.
- Ввести данные с Unicode (кириллица) — CSV должен сохранять корректно (UTF-8).
Безопасность, конфиденциальность и соответствие GDPR (заметки)
- CSV хранит данные в открытом виде — не используйте для чувствительных персональных данных.
- Для обработки персональных данных добавьте шифрование хранилища и аудирование доступа.
- Реализуйте экспорт/удаление данных по запросу пользователя, если работаете с данными EU граждан.
Исполнительные подсказки и эвристики
- Храните суммы как числа (float или Decimal для финансовой точности). Decimal предпочтительнее для точных финансовых расчётов.
- Даты храните в формате ISO (YYYY-MM-DD), для отображения применяйте локальное форматирование.
- При масштабировании переходите с CSV на SQLite, а затем на серверную СУБД.
Когда не нужно изобретать велосипед — готовые компоненты
- Используйте sqlite3 для быстрого перехода от CSV к базе без дополнительной установки.
- Для сложных GUI компонентов (таблицы, фильтры) рассмотрите PyQt/PySide.
Итог и рекомендации
Этот пример — хороший старт для персонального трекера расходов или учебного проекта. Если планируется продакшн‑решение с несколькими пользователями и требованиями к безопасности, замените хранение CSV на СУБД, добавьте авторизацию, шифрование и тестирование.
Короткая дорожная карта расширений:
- Валидация входных данных и локализация форматов.
- Импорт/экспорт в Excel; поддержка резервных копий.
- Замена хранения на SQLite; добавление фильтров и поиска.
- Синхронизация данных или веб‑версия при необходимости совместного доступа.
Короткое резюме:
Спасибо за внимание — начните с предложенного кода, затем по мере необходимости переносите логику хранения и интерфейс в более зрелые компоненты.
Похожие материалы
Основы сведения музыки — руководство
Как проверить скорость Wi‑Fi: полное руководство
Как записаться в Genius Bar — инструкция
Как создать прокси-сервер быстро и безопасно
Ошибка «Требуется аутентификация» в Google Play — как исправить