Узнайте, почему для десктопного приложения на Python выбрали Flask + браузер вместо PyQt или Electron. Получите практические инсайты для своего проекта.
Вы когда-нибудь задумывались, почему некоторые десктопные приложения на Python открываются в браузере, а не в нативном окне? Это не случайность, а осознанный архитектурный выбор. В этой статье я разберу четыре реальных подхода к созданию UI для Python-приложения, покажу, почему Flask + системный браузер победили, и раскрою неочевидные последствия такого решения.
Для инструмента автоматизации обслуживания WordPress на Python мы рассматривали четыре подхода:
| Подход | UI | Размер дистрибутива | Стоимость разработки | Доп. работа под ОС |
|---|---|---|---|---|
| Нативный (Swift / WPF) | Окна ОС | Малый–средний | Высокая (отдельная реализация под каждую ОС) | Большая |
| PyQt / PySide | Виджеты Qt | Средний (~80 МБ) | Средняя | Малая |
| Electron | Веб-UI на Chromium | Большой (~150+ МБ) | Средняя | Малая |
| Локальный Flask + браузер | Вкладка браузера | Малый (~50 МБ) | Средняя | Малая |
PyQt был серьёзным кандидатом. Python-стек привлекателен, но стили виджетов незаметно отличаются между ОС, система компоновки Qt требует постоянного внимания, а разрешение плагинов Qt под PyInstaller — муторное занятие. Скорость разработки оказалась ниже ожидаемой.
Electron — стандарт индустрии для кроссплатформенного UI, и HTML/CSS-интерфейсы пишутся быстро. Но дистрибутив весит более 100 МБ, а потребление памяти высокое. Для инструмента, который часто работает в фоне, такие накладные расходы неприемлемы.
Итоговая структура: Flask (лёгкий веб-фреймворк Python) + системный браузер для UI. Решение опиралось на три оси:
fabric/paramiko, автоматизация браузера через playwright, шифрование через cryptography — все ключевые библиотеки живут в экосистеме Python. Писать бэкенд на другом языке не было вариантом. Если Python уже требуется на бэкенде, размещение UI тоже на Python упрощает дистрибуцию.templates/index.html, а UI построен на Tailwind CSS и ванильном JS. Любой с опытом веб-разработки может быстро добавлять функции. Изучать каждый раз новый словарь нативных виджетов замедляет итерации гораздо сильнее.templates/ работают и под macOS .app, и под Windows .exe. Почти никакой дополнительной работы под ОС — это самый большой практический выигрыш.У этой структуры есть цена.
Вкладка браузера — это и есть UI. Если пользователь закрывает вкладку, приложение всё ещё работает, но до него не добраться. Повторный двойной клик по приложению не помогает, потому что macOS LaunchServices видит «приложение уже запущено» и просто переключает на него, не открывая новую вкладку.
Решение потребовало проверки живости на основе heartbeat в сочетании с самозатирающимся lock-файлом. (Подробнее в статье «когда macOS-приложение отказывается перезапускаться».)
Другие побочные эффекты: занят фиксированный порт (нужно обнаружение коллизий портов), приватный режим браузера ломает сессию входа и т.д. Ничего из этого не возникло бы с нативным окном.
«Локальный Flask + браузер» — не универсальное лучшее решение. Для приложений, которые сильно полагаются на нативные компоненты (центр уведомлений, иконка в трее, интеграция с keychain), или где запуск часто происходит офлайн, PyQt или нативный подход имеют больше смысла.
Но в условиях, которые реально стояли перед автоматизацией обслуживания WordPress — тяжёлый бэкенд, дашборд-подобный UI, малый дистрибутив, обязательная поддержка двух ОС — Flask + браузер оказался правильным балансом. Мы оптимизировали скорость разработки и размер дистрибутива, приняв другие компромиссы.
Побочные эффекты требуют отдельной тщательной обработки. Но в целом структура многократно окупилась за время жизни проекта.
Если вы создаёте десктопное приложение на Python с бэкенд-ориентированной логикой и простым дашбордом — попробуйте связку Flask + системный браузер. Вы получите малый размер дистрибутива и быструю разработку. Но не забудьте обработать кейс с закрытой вкладкой и коллизией портов. Начните с минимального прототипа:
from flask import Flask, render_template
import webbrowser
app = Flask(__name__)
@app.route('/')
def index():
return render_template('dashboard.html')
if __name__ == '__main__':
# Открываем браузер после запуска сервера
webbrowser.open('http://127.0.0.1:5000')
app.run(port=5000)
Этот код — основа, которую можно расширять под свои задачи.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →