Гид по технологиям

Учёт расходов на Python с Tkinter

8 min read Python Обновлено 01 Jan 2026
Учёт расходов на Python с Tkinter
Учёт расходов на 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()`
    

Как работать с приложением (пошагово)

  1. Запустите скрипт. Откроется окно с полями: сумма, описание, категория, дата.
  2. Введите сумму (например, 12.50), описание и дату в формате ГГГГ-ММ-ДД, нажмите Add Expense.
  3. Запись появится в списке; можно выбрать её и нажать Edit Expense или Delete Expense.
  4. Нажмите Save Expenses, чтобы экспортировать все записи в файл expenses.csv.
  5. Нажмите 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.

Выбор зависит от требований к масштабируемости, совместному использованию и доступности с устройств.

Мини‑методология разработки (шаги)

  1. Сформулировать минимально рабочий набор функций (MVP): добавление, список, экспорт, диаграмма.
  2. Написать UI с валидацией ввода (числа, даты).
  3. Добавить сохранение/загрузку (CSV → SQLite при необходимости).
  4. Написать тесты для критических функций (включая проверки парсинга чисел и дат).
  5. Добавить локализацию и настройку валюты.
  6. Провести обзор безопасности и приватности.

Роль‑ориентированные чеклисты

Разработчик:

  • реализовать валидацию суммы и даты;
  • обработать нечисловые значения и исключения при чтении/записи файлов;
  • написать модульные тесты для функций подсчёта и агрегации.

Тестировщик:

  • сценарии добавления/редактирования/удаления;
  • проверка корректности сумм (плавающая точка, округление);
  • проверка экспорта/импорта 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 на СУБД, добавьте авторизацию, шифрование и тестирование.

Короткая дорожная карта расширений:

  1. Валидация входных данных и локализация форматов.
  2. Импорт/экспорт в Excel; поддержка резервных копий.
  3. Замена хранения на SQLite; добавление фильтров и поиска.
  4. Синхронизация данных или веб‑версия при необходимости совместного доступа.

Короткое резюме:

Спасибо за внимание — начните с предложенного кода, затем по мере необходимости переносите логику хранения и интерфейс в более зрелые компоненты.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Основы сведения музыки — руководство
Аудио

Основы сведения музыки — руководство

Как проверить скорость Wi‑Fi: полное руководство
Сеть

Как проверить скорость Wi‑Fi: полное руководство

Как записаться в Genius Bar — инструкция
Ремонт устройств

Как записаться в Genius Bar — инструкция

Как создать прокси-сервер быстро и безопасно
Сеть

Как создать прокси-сервер быстро и безопасно

Ошибка «Требуется аутентификация» в Google Play — как исправить
Android.

Ошибка «Требуется аутентификация» в Google Play — как исправить

Android как GPS‑трекер: настройка и ограничения
Навигация

Android как GPS‑трекер: настройка и ограничения