Как создать трекер расходов с кроссплатформенным GUI на Python

Трекер расходов помогает записывать транзакции, составлять бюджеты, категоризировать траты и анализировать паттерны расходов. В этом руководстве вы узнаете, как шаг за шагом построить простое кроссплатформенное приложение на Python с графическим интерфейсом, экспортом в CSV и круговой диаграммой расходов.
Кому это полезно
- Новичкам в разработке настольных приложений на Python.
- Тем, кто хочет быстрый рабочий прототип учёта расходов.
- Разработчикам, которые планируют расширять функционал (база данных, синхронизация, аутентификация).
Что понадобится
Ключевые модули:
- Tkinter — стандартный GUI-инструментарий Python для настольных приложений.
- CSV — стандартная библиотека для чтения/записи CSV-файлов.
- Matplotlib — библиотека для визуализации (построение круговой диаграммы).
(Если потребуется хранение транзакций в базе — рассмотрите SQLite или PostgreSQL как альтернативу CSV.)
Установка (если Matplotlib не установлен):
pip install tk matplotlib Примечание: Tk обычно поставляется с большинством дистрибутивов Python; на некоторых системах (особенно Linux) может потребоваться отдельный пакет OS (например, python3-tk).
Структура приложения (кратко)
Архитектура приложения простая и содержит:
- Класс приложения ExpenseTrackerApp (наследник tk.Tk).
- Внутренние структуры: список expenses (список кортежей) и список категорий.
- Виджеты ввода: поля суммы, описания, комбобокс категории, поле даты.
- Список (Listbox) для отображения записей и набор кнопок: добавить, редактировать, удалить, сохранить, показать диаграмму.
- Экспорт в CSV и построение круговой диаграммы по категориям с помощью Matplotlib.
Ниже — исходные фрагменты кода из примера (сохранены без изменений для точности).
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 добавляет на окно метки, поля ввода, комбобокс, кнопки и listbox. В коде задаются шрифты, ширины и базовая компоновка через grid/pack. Автор использует StringVar для привязки выбранной категории.
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)Далее — кнопки, listbox и метод обновления общего итога:
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()Основной функционал (логика)
Программа использует ряд методов, которые реализуют добавление, редактирование, удаление, обновление списка, сохранение в CSV и построение диаграммы.
Добавление расхода (add_expense): проверяет поля, добавляет запись в self.expenses и в Listbox, очищает поля:
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()Редактирование (edit_expense) — открывает диалог и заменяет сумму выбранной записи:
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()Удаление (delete_expense): удаляет выбранную запись:
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()Обновление списка (refresh_list) и подсчёт общего итога (update_total_label):
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))Визуализация по категориям (show_expenses_chart): собирает суммы по категориям и строит круговую диаграмму:
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()Скриншоты интерфейса




Улучшения и проверочные практики (рекомендации)
Ниже собраны практические рекомендации по повышению надёжности, удобства и безопасности приложения.
Валидация и формат данных
- Проверяйте, что сумма — валидное положительное число. Используйте try/except при преобразовании в float.
- Для даты применяйте модуль datetime и строгий формат (например, YYYY-MM-DD). Пример: datetime.datetime.strptime(date_str, “%Y-%m-%d”).
- Проверяйте длину и содержимое полей описания (защита от некорректного ввода).
Хранение данных: CSV vs БД
- CSV удобен для простых прототипов и совместимости с Excel.
- Для многопользовательских или транзакционных сценариев используйте SQLite (local) или PostgreSQL (серверно) — это даст целостность данных, запросы и фильтрацию по времени.
Локализация и валюты
- UI-строки в коде можно локализовать, например, через gettext или собственную таблицу переводов.
- Для формата суммы используйте локализованные форматеры (locale.currency) или babel.numbers для контроля группировки разрядов и символов валют.
- Если приложение работает в разных регионах, храните суммы и валюту как отдельные поля (amount, currency).
Безопасность и приватность
- CSV-файл не шифруется — если данные приватны, храните их в зашифрованном контейнере или используйте защищённую БД.
- Не храните чувствительные данные (например, номера банковских карт) в открытом виде.
- Для синхронизации с облаком применяйте авторизацию и TLS.
- Примечание по GDPR/локальным законам: если приложение обрабатывает персональные данные пользователей, необходимо обеспечить возможность удаления и экспорта персональных данных по запросу.
UX и дополнительные функции
- Добавьте поиск и фильтрацию (по описанию, сумме, категории, дате).
- Возможность сортировки по дате/сумме/категории.
- Поддержка регулярных транзакций и напоминаний (уведомления).
- Импорт CSV (чтобы подтягивать данные из банков или других приложений).
Альтернативные подходы
- GUI-фреймворки: PyQt / PySide / Kivy — если нужен современный интерфейс и более сложные виджеты.
- Web-приложение: Flask / FastAPI + React/Vue — если нужна мультиплатформенная веб-версия и удалённый доступ.
- Хранилище: SQLite для локальных пользователей; ORM (SQLAlchemy) упрощает миграции.
Контроль качества: тесты и критерии приёмки
Критерии приёмки:
- Приложение запускается без ошибок в целевой среде.
- Можно добавить запись с суммой и датой; запись появляется в списке.
- Редактирование изменяет данные и обновляет итог.
- Удаление убирает запись из списка и из внутренних данных.
- Экспорт в CSV создаёт файл expenses.csv с корректными заголовками и значениями.
- Построение диаграммы не падает при нечисловых суммах (такие записи игнорируются).
Тестовые сценарии (ручные / автоматизированные):
- Добавление нескольких записей с разными категориями и проверка суммы.
- Попытка добавить запись с пустыми полями — ожидается предупреждение.
- Редактирование записи с вводом некорректной суммы — ожидается откат или валидация.
- Сохранение/загрузка CSV: проверить соответствие строк и заголовков.
- Построение диаграммы при отсутствии записей — должно корректно показывать сообщение об отсутствии данных или пустую диаграмму.
Чек-листы по ролям
Разработчик:
- Добавить валидацию суммы и даты.
- Сделать сохранение атомарным (временный файл + переименование).
- Добавить логирование ошибок.
QA:
- Покрыть тестами основные сценарии: add/edit/delete/save/chart.
- Тестировать на разных ОС: Windows, macOS, Linux.
Пользователь:
- Перевести интерфейс на предпочитаемый язык.
- Проверить экспорт/импорт CSV в своей локали (разделитель, формат даты).
Миграция и совместимость
- Файловая структура: expenses.csv — простая таблица с четырьмя колонками (сумма, описание, категория, дата). При переходе на базу данных создайте соответствующие колонки и мигрируйте данные одним запросом.
- Версии Python: код совместим с Python 3.6+ (f-строки, модуль datetime и т.д.). Тестируйте на целевых версиях.
Примеры улучшений: краткий чек-лист внедрения
- Валидация полей (числа и даты).
- Локализация UI-строк.
- Импорт/экспорт CSV с выбором разделителя.
- Переход на SQLite при большом объёме данных.
- Шифрование файлов/базы при необходимости приватности.
Частые ошибки и как их избежать
- Ошибка: ValueError при суммировании — решение: фильтровать записи с нечисловыми суммами и логировать некорректные строки.
- Ошибка: повреждённый CSV — решение: писать через временный файл и атомарное переименование.
- Проблема UX: длинные описания обрезаются в Listbox — решение: добавлять превью и отдельное окно с полными деталями.
Шаблон CSV (заголовки)
Первая строка CSV должна быть:
Expense Amount,Item Description,Category,Date
Каждая последующая — одна запись с соответствующими значениями.
Превью для социальных сетей
- OG Title: Как создать трекер расходов на Python
- OG Description: Пошаговое руководство по созданию настольного трекера расходов на Python с Tkinter, экспортом в CSV и визуализацией через Matplotlib.
Итог
Этот проект — отличный старт для практики GUI-разработки на Python и быстрой итерации продукта. Для простых личных нужд CSV + Tkinter достаточно; для надёжных бизнес-решений стоит перейти на базу данных, добавить аутентификацию и защищённое хранение данных.
Ключевые направления дальнейшей работы: валидация и локализация, импорт банковских выписок, уведомления о превышении бюджета и синхронизация с облаком.
Похожие материалы
Как скрыть папку или файл в Windows и Mac
Как отменить подписку YouTube Premium
Google Photos: руководство по использованию
Видеоняня через Skype — дешёвый и простой способ
Как пользоваться встроенным словарём iPhone