От input() к UI после вводного курса по Python
Неоригинальное начало.
Начинающий python разработчик недавно попросил меня посмотреть «что нужно улучшить» в программе, написанной им по гайду «Python с нуля». Таких гайдов, курсов и туториалов доступно великое множество.
Консольное приложение ведения списка пользователей. Вывести список, добавить, отредактировать, удалить пользователя.
Парень не ИТ-ник, учится на экономической специальности. Приложение было написано аккуратно и работало.
В чем проблема? Интерфейс пользователя.
Конечно, в коде были участки для рефакторинга. Где ж и их нет.
Но зададим себе вопрос:, а вы бы хотели пользоваться таким приложением? Ответ очевиден — нет. И причина в UI (интерфейсе пользователя).
Консольные интерактивные интерфейсы пользователя были актуальны для mainframe в 80-х годах прошлого века, а то и ранее.
Командная строка живее всех живых. Но паттерн использования другой. Утилиты командной строки запускаются, выполняют задачу, выводят результат и быстро завершаются.
Ближайшим аналогом разработанного приложения являются утилиты командной строки для управления пользователями операционной системы. Системному администратору хорошо. А вот обычным пользователям такое приложение «не зайдет».
Где tutorial по самому крутому UI? Не крутой, а доступный тебе для освоения.
Казалось бы решение лежит на поверхности.

Но почему в ходе учебного курса предложили сделать именно такой интерфейс?
Все просто. Консольный интерфейс с использованием вызовов input () крайне прост по своей сути. Это позволяет обучающемуся сосредоточиться на понимании базовых структур данных и конструкций языка.
Ограничение в скорости усвоения знаний.

Современные ИТ-технологии не сразу стали такими. С каждым витком развития происходило их усложнение. В какие то моменты времени «сложность пряталась» внутрь библиотек и фреймворков.
Даже общее понимание идей и концепций при наличии готовых библиотек требует времени.
А скорость усвоения знаний человеком весьма конечна
Применительно к нашему кейсу: изучив базовый курс по Python, у новичка не получится в короткий срок научиться делать интерфейсы общения с пользователем на естественном языке, как у ИИ.
Вряд ли получится сделать современный Web интерфейс на React/Vue.js и аналогичных Javascript фреймворках.
Может быть получится сделать классический Web интерфейс c генерацией статичного HTML на серверной стороне.
А вот десктоп приложение с графическим оконным интерфейсом (GUI) сделать скорее всего получится.
Десктоп приложения с оконным интерфейсом были следующей примечательной стадией развития способов взаимодействия с пользователем. Их расцвет пришелся на 1990-е годы.
Выбираю GUI-библиотеку и делаю? Рано. Хороший фасад не бывает без хорошей изнанки.
GUI приложение станет сложнее, чем консольное. К этому нужно приготовиться.
Хочешь писать сложные программы — учись их структурировать.

Структура GUI приложения. Разделяй и властвуй.
Показанная структура достаточно практична, но не является единственно верной «серебряной пулей».
Постарайся понять подход и идею.

Фронтальная часть приложения (front или фронт) занимается отображением и вводом данных пользователя.
Подложка (back или бэк) реализует логику обработки данных. В нашем случае создание, удаление, редактирование, получение и хранение списка пользователей.
Фронт про отображение, бэк про логику обработки.
Бэк состоит из:
Model — набор сущностей предметной области. В нашем случае одна сущность User (Пользователь).
Service — точка входа для фронта, предоставляющая логику работы с сущностью предметной области. В нашем случае это методы создания, удаления, редактирования пользователя и получения списка пользователей.
Validator — логика проверки корректности единичной сущности. Используется Service.
Repository — хранилище списка пользователей. Используется Service
Структура приложения является разновидностью паттерна проектирования MVC (Model-View-Controller)
Кончай дедукцию, давай продукцию.
С целью и способом достижения определились. Пора начинать реализацию.
Я подсказывал начинающему программисту куда двигаться, но намеренно не писал готовый код.
Продолжу данный подход и в статье.
Какие знания обновить.
Потребуется обновить (или приобрести) базовые знания ООП (объектно-ориентированного программирования): класс, конструктор класса, методы, поля, понимание наследования.
Полезно иметь начальные знания по теме модулей в python. Желательно уметь пользоваться type hints.
Модель и валидатор. На страже корректности полей.
Модель (model) состоит из одного класса User.
class User:
def __init__(self, id: int, first_name: str, last_name: str):
self.id = id # уникальный идентификатор пользователя
self.first_name = first_name # имя пользователя
self.last_name = last_name # фамилия пользователя
Валидатор (validator) — Проверяет фамилию и имя на заполненение данными и наличие целочисленного id у пользователя.
class UserValidator:
def validate(self, user: User):
# Проверки:
# Имя и фамилия должны быть непустой строкой
# без цифр и спец символов
# id должно быть целым числом
...
return [] # вернуть пустой список, если ошибки не обнаружены
Репозиторий. Храни и выбирай.
Исходная программа хранила список пользователей в памяти. Был реализован репозитарий (repository) — обертка над питоновским списком с автогенерацией id пользователя при вставке.
class UserRepository:
def add_user(self, user: User):
# сгенерировать id, user.id = gen_id
# сохранить пользователя в списке
pass
def update_user(self, user: User):
# если пользователь не найден по id, то ошибка
pass
def delete_user(self, user: User):
pass
def list_all(self):
# вернуть список пользователей
pass
def find_by_id(self, id: int) -> User | None:
# Найти пользователя по id
pass
def find_by_names(self, first_name: str, last_name: str):
# True - есть пользователь с таким именем и фамилией, иначе False
pass
Сервис бэку голова.
Сервис (service) — Реализует возможность создания, редактирования, удаления пользователя, а также получение списка пользователей. Использует валидатор и репозиторий.
class UserService:
def __init__(self, validator: UserValidator, repo: UserRepository):
self.validator = validator
self.repo = repo
def create_user(self, first_name: str, last_name: str):
user = User(0, first_name, last_name)
errors = self.validator.validate(user)
# Если найдены ошибки в атрибутах пользователя,
# то вернуть ошибку
...
founded_user = repo.find_by_names(first_name, last_name)
# если пользователь с именем и фамилией уже есть - ошибка
...
# все ок. добавляем пользователя в список пользователей
self.repo.add_user(user)
return user
def update_user(self, user: User):
# Проверть, что пользователь с id есть в списке
existed_user = self.repo.find_by_id(user.id)
if existed_user is None:
...
# проверить валидность обновляемого пользователя
errors = self.validator.validate(user)
if len(errors) > 0:
...
# проверить, что нет пользователя с другим id,
# но таким же именем и фамилией
self.repo.find_by_names(user.first_name, user.last_name)
self.repo.update_user(user)
def delete_user(self, user: User):
pass
def list_all_users(self):
return self.repo.list_all()
В качестве промежуточного результата консольное приложение было отрефакторено для использования бэка без изменения консольного UI.
Библиотека UI. PySide.
Для реализации фронта выбрали фреймворк PySide по причине популярности и большого количества гайдов в сети.
Главный экран GUI был написан с помощью виджета QtTable, кнопок QPushButton. Формы создания и редактирования пользователя построены на базе QDialog.
Трудности первых шагов были успешно преодолены.

Что в итоге? Шаг из 80-х в 90-е за три недели.
В течении трех недель самостоятельных занятий по вечерам и созвонов раз два-три дня приложнение приобрело не идеальный, но работающий GUI и бэк.
Стало ли приложение лучше? Да.
Для конечного пользователя качественный скачок очевиден. С точки зрения программирования непаханое поле отзывов «да я сделаю проще и лучше в сто раз».
Является ли предложенный путь «единственно правильным»? Нет. Всего лишь один из вполне рабочих вариантов.
Главная ценность кейса в возможности за две-три недели после базового курса по python повысить практическую применимость навыков.
Плохая новость в том, что достичь мастерства легко и за один подход не получится.
Хороших новостей больше:
Писать десктопные приложения не так страшно. Хотя и далеко не всегда актуально.
Целеполагание («нужно делать правильные вещи»), проектирование («нужно делать вещи правильно») и ритмичность развития («регулярно фиксировать прогресс, искать точки роста») три кита профессионального успеха.
Куда дальше? Полезные ссылки и идеи.
В этом разделе я постарался собрать полезные идеи и материалы, относящиеся к материалу кейса.
Развитие «глазами пользователя».
После закрытия программы данные пропадают, так как хранятся только в памяти. Смотреть в сторону встраиваемых баз данных, например, SQLLite или «взрослой» БД PostgreSQL. Полезно изучить SQL.
Необходимы улучшения UI — меню, popup меню, inplace edit итд. Материалов по PyQt/PySide в сети много. Какой из них лучше трудно сказать. Выбирайте сами. Как один из примеров читать тут.
Способ распространения приложения — скачивание из интернет с установкой python на машину пользователя. Неудобно. Можно посмотреть в сторону PyInstaller и Nuitka для сборки в исполняемый файл.
Развитие «глазами программиста».
Дорожные карты прокачки навыков
Хорошая статья на Хабр
Англоязычный roadmap
Развитие кейса
Популярная библиотека валидаторов Pydantic
Материалы по паттерну проектирования Model-View-Controller. Еще один пример, но на другом UI framework
MVC средствами PySide
Модульное тестирование приложений. библиотеки unittest и pytest, модуль pytest-qt