Прямой ответ
Выбор между Tkinter и PySide6 — про масштаб интерфейса: Tkinter (стандартная библиотека, нулевые зависимости) достаточен для простых утилит — форма, кнопки, таблица, диалоги файлов; PySide6 (официальные биндинги Qt) — для продуктовых приложений: сложные виджеты, темы, графику, многооконность. Оба работают по event-loop-модели, и главное правило одинаково: долгие операции — вне GUI-потока (иначе интерфейс замерзает на секунды — потоки и GIL в статье про многопроцессность). И честный вопрос до старта: нужен ли вообще десктоп — для многих инструментов веб-интерфейс (локальный сервер + браузер) или TUI/CLI дешевле в разработке и дистрибуции.
Tkinter: стандартный минимум
Tkinter входит в поставку Python — «import tkinter» работает везде без установки (редчайшее свойство); виджеты — базовые (Button/Entry/Treeview/диалоги), ttk-тема делает вид приемлемым. Сильные стороны: порог входа — вечер, зависимость — ноль, для внутренних утилит («кнопка к скрипту») идеален. Ограничения: примитивная компоновка на больших формах, нет встроенных «взрослых» компонентов (редактор с подсветкой, док-панели), внешний вид утилитарный, документация разрозненная. Практический потолок Tkinter — инструменты на 3–10 экранов для себя/команды; выше — Qt оправдан.
PySide6: Qt во всей мощи
PySide6 — LGPL-биндинги Qt 6: весь зоопарк виджетов (таблицы с сортировкой/фильтрами, графики QtCharts, веб-вью, док-системы), сигнально-слотовая модель событий (типизированные сигналы вместо коллбэк-лапши), Qt Designer — визуальная компоновка форм (.ui-файлы), стили/темы. Цена: тяжёлая зависимость (сотни МБ), LGPL-нюансы при проприетарной дистрибуции (динамическая линковка — обычно достаточно; PySide6, в отличие от PyQt, лицензионно проще), кривая входа — недели (но документация Qt покрывает всё). Архитектурный паттерн Qt-приложения: UI-слой (виджеты) отделён от логики (QObject-сервисы с сигналами) — тестировать логику можно без GUI; «всё в обработчике кнопки» в Qt масштабируется плохо.
Event loop и долгие операции: главный грабель
GUI живёт в event loop: обработчик события, занявший 5 секунд, замораживает окно на 5 секунд (ОС рисует «не отвечает»). Правила: тяжёлая работа — в QThread/QThreadPool+worker (Qt) или threading.Thread (Tkinter) с результатами, возвращаемыми в GUI-поток через сигналы/after() (трогать виджеты из рабочего потока нельзя — гонки и падения); прогресс — сигнал-прогрессбар (не «молча крутить минуту»); отмена — кооперативная (флаг, который worker проверяет). Для CPU-тяжёлого в worker-потоке помните про GIL — процессы/пул (разбор). Этот паттерн (UI-поток лёгкий, работа в фоне, общение событиями) универсален — он же в браузерном JS и мобильных платформах.
Дистрибуция: боль, о которой молчат туториалы
«Работает у меня» в десктопе упирается в упаковку: PyInstaller/Nuitka собирают приложение + интерпретатор + зависимости в standalone-бандл (exe/dmg/AppImage) — размер десятки-сотни МБ (Qt — тяжелее), антивирусы косятся на неподписанные exe (код-подпись — отдельная история), обновления — ваша задача (автоапдейтер или ручной релиз-цикл). Альтернативная дистрибуция: приложение в docker с веб-мордой (обновление — pull), pipx-установка для коллег-разработчиков (CLI+GUI, Python у них есть — про упаковку). Тестирование на чистой машине/VM до релиза обязательно — отсутствующая DLL на «чистой Windows» находится только там.
Честные альтернативы десктопу
Прежде чем писать GUI, рассмотрите: локальный веб-сервис (FastAPI + браузер — весь инструментарий веба, дистрибуция нулевая, «десктопность» — ярлык с --app-режимом Chrome); TUI (textual/rich — полноценные интерфейсы в терминале, нулевая дистрибуция для разработчиков, SSH-дружелюбность); CLI + системный файловый менеджер (многие «GUI-задачи» решаются drag-n-drop на скрипт или контекстным меню); Jupyter (для аналитических инструментов — интерактивность без разработки GUI). Десктоп-фреймворк оправдан, когда нужны нативность (трей, глобальные хоткеи, оффлайн-системные интеграции), сложные виджеты или аудитория без браузера/терминала.
Практический сценарий: утилита конвертера файлов
Соберём характерную утилиту «выбрал папку — сконвертировал файлы — показал результат», чтобы увидеть разницу подходов в коде. На Tkinter это примерно сто строк: filedialog.askdirectory для выбора, ttk.Treeview со списком файлов и статусами, кнопка запуска, которая стартует threading.Thread с циклом конвертации — поток после каждого файла вызывает root.after(0, update_row, i, status) (единственный легальный способ тронуть UI из потока — через очередь главного цикла), а по завершении — общий итог. Всё, утилита работает, зависимость — ноль, распространение коллегам-разработчикам — «запусти скрипт». Та же утилита на PySide6 оправдана, когда появляются требования: drag-and-drop файлов в окно, прогресс-бар с отменой (worker в QThreadPool с сигналом progress и кооперативной отменой по флагу), сохранение настроек (QSettings — последние директории переживают перезапуск), системный трей с фоновой работой, темы и адаптивная раскладка под размеры окна. Код вырастет до нескольких сотен строк, но каждая фича — готовый компонент Qt, а не самописный. Порог перехода практический: пока утилита «для себя» и на три кнопки — Tkinter покрывает; как только появляется продуктовость (красивый вид, состояния, системные интеграции) — Qt окупает свою тяжесть, а самописные костыли поверх Tkinter обходятся дороже.
Практический сценарий: упаковка PySide6-приложения под Windows
Финальный этап, на котором ломается половина самописных десктопов, — дистрибуция. Порядок для PyInstaller: пишется spec-файл (или первый прогон pyinstaller --windowed --name Tool app.py), точка входа — скрипт, который сам поднимает QApplication; --windowed отключает консольное окно (для GUI обязательно, иначе рядом с окном висит чёрная консоль). Первый запуск собранного exe почти всегда падает на отсутствующих ресурсах — иконки, .ui-файлы, QML-модули не попали в бандл: добавляются через datas/hiddenimports в spec; Qt-плагины платформ обычно подхватываются хуками PyInstaller автоматически, но проверяется это только прогоном. Второй прогон — на чистой виртуальной машине без Python: именно там находятся отсутствующие DLL и права на запись в Program Files (данные приложения пишем в %APPDATA% через QStandardPaths, не рядом с exe). Третий — антивирусная реальность: неподписанный exe SmartScreen показывает с предупреждением; лечится код-подписью (сертификат — отдельная статья расходов) или, для внутренних утилит, инструкцией и установкой через корпоративный реестр. Автообновление — своя задача (проверка версии по URL, скачивание, перезапуск); до её появления — честная ссылка на страницу релизов и tool --version в окне About. После этих трёх прогонов приложение действительно «устанавливается у всех» — разница с «работает у меня» оказывается примерно неделей работы, которую лучше закладывать в план сразу.
FAQ
PySide6 или PyQt6? Функционально близнецы (оба Qt 6); PySide6 — официальный от Qt Company с LGPL (свободнее для проприетарных продуктов), PyQt6 — GPL/коммерческая. Новым проектам обычно PySide6; миграция между ними — механическая (импорты).
Можно ли на Tkinter сделать «красиво»? Приемлемо — да (ttk-темы, кастомные цвета), «как нативное современное приложение» — нет. Если вид продукта важен — Qt или веб.
Kivy/BeeWare вместо них? Kivy — свой рендер (не нативные виджеты), ниша — тач/мобильные; BeeWare — обёртка нативных компонентов, проект молодой. Для классического десктопа Qt/Tkinter остаются практичнее по зрелости и документации.
Как тестировать GUI-приложение? Пирамида сохраняется: логику — юнитами (отделена от виджетов), GUI — дымовыми тестами через QTest/pytest-qt (клики, состояния) или pyautogui для сквозных; основную уверенность даёт разделение слоёв, а не UI-тесты.
Источники: документация Qt for Python, tkinter (docs.python.org), PyInstaller — ссылки в шапке.