ФронтендМобильные приложенияPython

Как сделать приложение на Python через Flet: простой трекер расходов

Создаём трекер расходов на Flet: поля, кнопка, список покупок, проверка Decimal, общая сумма и запуск одного Python-кода в окне или браузере.

Кодик

Автор

7 мин чтения

Flet позволяет собрать интерфейс из Python-контролов и запускать его как desktop или web-приложение. Сделаем трекер расходов с названием, суммой, списком и итогом. Разберём обработчик on_click, обновление контролов, Decimal и ошибку ввода без перехода на HTML и JavaScript.

Консольный скрипт считает сумму, но не показывает жизненный цикл интерфейса. Flet соединяет знакомые функции Python с полями и кнопками. В этой статье вы соберёте окно трекера с добавлением и удалением расходов. Финальный результат можно проверить без догадок: валидная покупка появляется в списке, неверная сумма показывает ошибку, удаление пересчитывает total.

1Controls

TextField, Button, ListView и Text являются Python-объектами.

2Events

on_click вызывает функцию с событием и меняет состояние.

3Update

После изменения controls страница получает новый вид через update.

Как Flet превращает Python в интерфейс

main получает Page и создаёт controls. Список expenses хранит данные отдельно от визуальных Row. render_list пересобирает ListView и total. Обработчики add_expense и delete_expense меняют список, затем вызывают render_list. Для Flet держите этапы раздельно: ввод, проверка, основное действие и вывод. Такой маршрут позволяет остановиться в конкретной точке, увидеть фактические данные и исправить одну причину вместо случайной перестановки строк.

ЭтапДействиеПроверяемый результат
Запускft.run вызывает mainPage готова к controls
ВводПользователь заполняет два TextFieldЕсть name и amount
СобытиеButton вызывает add_expenseДанные проверены и добавлены
Рендерrender_list обновляет controlsСписок и total совпадают

Данные являются источником истины, а элементы списка каждый раз строятся из expenses. У Flet каждый этап оставляет наблюдаемый след, поэтому следующий шаг начинается только после проверки предыдущего.

Поток данных Flet-трекера
От входных данных до видимого результата. Каждый event меняет data до UI

Не храните сумму только в тексте интерфейса. Total вычисляйте из списка расходов. Если менять подпись вручную в каждом обработчике, удаление и будущая загрузка файла быстро рассинхронизируют результат.

Пишем трекер расходов

Flet требует поддерживаемый Python. Создайте проект через uv, добавьте runtime и инструменты по актуальной установке Flet, затем запустите uv run flet run main.py. Для браузерного режима используйте uv run flet run --web main.py. Первый desktop-запуск может загрузить клиентские компоненты.

from decimal import Decimal, InvalidOperation
from uuid import uuid4

import flet as ft


def main(page: ft.Page):
    page.title = "Expense tracker"
    page.padding = 24
    expenses: list[dict] = []

    name = ft.TextField(label="Purchase", expand=True)
    amount = ft.TextField(label="Amount", width=160)
    message = ft.Text(color=ft.Colors.RED_600)
    total = ft.Text("Total: 0.00", size=24, weight=ft.FontWeight.BOLD)
    items = ft.ListView(spacing=8, height=320)

    def render_list():
        items.controls.clear()
        for expense in expenses:
            items.controls.append(
                ft.ListTile(
                    title=ft.Text(expense["name"]),
                    subtitle=ft.Text(f'{expense["amount"]:.2f}'),
                    trailing=ft.IconButton(
                        icon=ft.Icons.DELETE_OUTLINE,
                        data=expense["id"],
                        on_click=delete_expense,
                    ),
                )
            )
        value = sum(item["amount"] for item in expenses)
        total.value = f"Total: {value:.2f}"
        page.update()

    def delete_expense(event: ft.Event[ft.IconButton]):
        expenses[:] = [item for item in expenses if item["id"] != event.control.data]
        render_list()

    def add_expense(_event):
        message.value = ""
        try:
            value = Decimal(amount.value.replace(",", "."))
            if value <= 0:
                raise ValueError
        except (InvalidOperation, ValueError, AttributeError):
            message.value = "Enter a positive amount"
            page.update()
            return
        clean_name = (name.value or "").strip()
        if not clean_name:
            message.value = "Enter a purchase name"
            page.update()
            return
        expenses.append({"id": str(uuid4()), "name": clean_name, "amount": value})
        name.value = ""
        amount.value = ""
        render_list()

    page.add(
        ft.Text("Expenses", size=34, weight=ft.FontWeight.BOLD),
        ft.Row([name, amount, ft.FilledButton("Add", on_click=add_expense)]),
        message,
        items,
        total,
    )


ft.run(main)
Что должно произойти после запуска
  • Покупка и положительная Decimal добавляются в expenses.
  • Ошибка остаётся рядом с формой и не падает в terminal.
  • Delete использует стабильный uuid записи.
  • Total каждый раз считается из актуального списка.

Decimal сохраняет понятное денежное значение, а строка с запятой нормализуется до преобразования. Пример Flet специально ограничен одним пользовательским сценарием, чтобы назначение каждой строки оставалось видимым.

Controls и отдельное состояние
Что отличает устойчивый проект от случайного запуска. Сериализуемые записи не зависят от Flet

Где хранить state и когда вызывать update

Flet создаёт дерево controls на стороне Python и синхронизирует изменения с Flutter-клиентом. Событие приходит в обработчик с event.control, поэтому data удобно связывает кнопку удаления с id. Изменение свойства value само по себе не гарантирует моментальный вид: page.update отправляет накопленные изменения. Для маленького локального приложения список в замыкании main достаточен, но после перезапуска он пуст. Постоянство требует файла, SQLite или client storage и отдельной загрузки.

ЧастьЗа что отвечаетЧто проверить
PageХранит экран и controlsTitle и padding заданы
TextFieldСобирает строки пользователяПустые значения обработаны
ListViewПоказывает расходыControls пересобраны из data
EventСвязывает click и handlerDelete получает id
DecimalХранит суммуПоложительность проверена

Полное items.controls.clear удобно для десятков строк, но большой список лучше обновлять точечно. Обработчик не должен выполнять долгую сеть синхронно, иначе интерфейс замрёт. Асинхронные задачи запускают через поддерживаемый механизм Page. Типы control и event меняются между версиями библиотеки, поэтому сверяйтесь с актуальными docs. Для мобильной сборки потребуется platform toolchain, подпись и отдельные permissions, а flet run на desktop является лишь этапом разработки.

Не смешивайте данные и Controls в одном списке. expenses хранит name и Decimal, а ListView строится отдельно. Тогда данные можно сохранить в JSON или базе без сериализации визуальных объектов.

Проверяем суммы, удаление и пустой ввод

Добавьте три расхода, один удалите и сверните total вручную. Введите abc, -5, пустую строку и число с запятой. Добавьте category через Dropdown и покажите её в subtitle. Затем сохраните expenses в JSON при каждом изменении и загрузите при старте, преобразуя amount обратно в Decimal. Создайте кнопку Clear all с подтверждением. После этого запустите тот же код с --web и сравните layout на узком окне.

Проверка своими руками
  1. Добавьте две валидные покупки.
  2. Проверьте сумму с копейками.
  3. Отклоните abc и отрицательное число.
  4. Удалите одну запись.
  5. Добавьте category.
  6. Перезапустите web-режим.
Готово, если выполняются все пункты
  • Форма валидируется.
  • Список строится из data.
  • Удаление пересчитывает total.
  • Desktop и web запускаются.

Интерфейс остаётся простым, пока список расходов можно проверить отдельно от Flet controls. После этой проверки измените одно правило Flet и заранее запишите ожидаемый результат. Так копирование превращается в понимание.

Четыре проверки приложения расходов
Четыре проверки перед следующим шагом. Сумма проверяется после каждого удаления

Как развивать Flet-приложение дальше

Следующим шагом вынесите Expense в dataclass, сохранение в отдельный repository и добавьте тесты чистых функций. Для постоянного хранения выберите SQLite или JSON по объёму и риску одновременной записи. Не записывайте файл на каждый символ TextField. На мобильном устройстве учитывайте путь приложения и permissions. Перед упаковкой проверьте актуальную команду flet build, иконки и подпись для каждой платформы.

Как изучить тему в Кодике

Повторите функции, списки словарей, Decimal и исключения Python, затем свяжите их с controls и events. В Кодике сначала восстановите чистую логику без фреймворка, затем верните библиотеку и интерфейс Flet.

ШагЧто разобратьМини-проверка
1Списки и словариДобавить Expense
2DecimalПроверить сумму
3ФункцииВынести render_list
4СобытияУдалить по data
5ХранениеСохранить JSON или SQLite

Сначала тестируйте расчёт total как чистую функцию, потом подключайте визуальный слой. Финальная проверка для Flet: закрыть статью, объяснить путь данных и добавить одну функцию без копирования готового блока.

Сумма хранится только в Text

Пересчитывайте её из списка данных.

Используют float без решения

Для учебных денег берите Decimal и явный формат.

Controls смешаны с data

Храните сериализуемые записи отдельно от UI.

Долгая задача в on_click

Не блокируйте интерфейс синхронной сетью или тяжёлым циклом.

Сверяйтесь с первичной документацией. Команды и ограничения примера проверены по официальной установке Flet. Если версия инструмента изменилась, официальный источник важнее старого скриншота.

Что получится в итоге

Готовый результат проекта: Как сделать приложение на Python через Flet: простой трекер расходов
Форма принимает покупку, список обновляется, удаление работает по id, а итоговая Decimal-сумма остаётся согласованной.

Короткие ответы
Как запустить Flet-приложение в браузере?

Используйте flet run --web для того же Python-файла.

Когда Flet показывает изменённые controls?

После обновления свойств вызовите page.update или обновление соответствующего control.

Почему расходы хранятся отдельно от ListView?

Отдельные данные легко считать, тестировать и сохранять без визуальных объектов.

Как понять, что учебный проект Flet действительно работает?

Проверьте основной сценарий Flet, неверный ввод, повторный запуск и один граничный случай. Затем объясните путь данных своими словами.

Можно ли начать с готового кода Flet из статьи?

Да. Сначала добейтесь результата Flet, затем измените одно правило и восстановите ключевой файл без копирования.

Постройте интерфейс вокруг уже понятной Python-логики

Python, списки и функции закрепите в курсе Python. Сравните подход с калькулятором на Tkinter.

Проверку ввода дополните материалом про Pydantic, а генерацию безопасных значений разберите в генераторе паролей.