Введение
Весь предыдущий план статей разбирал Qt через C++ — но Qt доступен и из Python через две конкурирующие библиотеки-обёртки: PySide6 (официальная, разрабываемая самой компанией Qt) и PyQt6 (от Riverbank Computing, существующая значительно дольше). В этой первой статье нового цикла разберём их различия, лицензионные особенности, и критерии выбора для конкретного проекта.
Концепция
PySide6 и PyQt6 оба предоставляют практически идентичный по составу доступ к классам и функциям Qt — для подавляющего большинства кода переход с одной библиотеки на другую требует лишь замены пары импортов и нескольких синтаксических деталей, поскольку обе библиотеки оборачивают один и тот же, общий набор библиотек Qt. Главное различие — лицензия: PySide6 распространяется под LGPL, что позволяет использовать её в закрытом, коммерческом, не раскрывающем исходный код приложении без необходимости покупки коммерческой лицензии (при соблюдении условий LGPL о динамической линковке); PyQt6 распространяется под GPL (с возможностью покупки отдельной коммерческой лицензии у Riverbank Computing для случаев, когда условия GPL неприемлемы для конкретного коммерческого проекта). Эта разница в лицензировании на практике часто становится решающим фактором выбора между ними для коммерческих, не раскрывающих исходный код проектов.
Пример кода: PySide6 vs PyQt6
# PySide6 — типичные импорты
from PySide6.QtWidgets import QApplication, QPushButton
from PySide6.QtCore import Signal, Slot
# PyQt6 — практически идентичный код, отличающийся только именем пакета
from PyQt6.QtWidgets import QApplication, QPushButton
from PyQt6.QtCore import pyqtSignal as Signal, pyqtSlot as Slot
# ^ имена Signal/Slot в PyQt6 называются pyqtSignal/pyqtSlot —
# одно из немногих, но заметных синтаксических отличий между двумя библиотеками
# Незначительные различия в поведении перечислений (enum) между библиотеками
from PySide6.QtCore import Qt
# PySide6 поддерживает как полную, так и сокращённую форму доступа к значениям перечисления
alignment_full = Qt.AlignmentFlag.AlignCenter
alignment_short = Qt.AlignCenter # сокращённая форма также работает в PySide6
# PyQt6 в более новых версиях требует ИМЕННО полной формы (Qt.AlignmentFlag.AlignCenter),
# что является одним из примеров более строгого соответствия PyQt6 духу современного Python
# Пример кода, написанного с минимальной зависимостью от конкретной библиотеки —
# практичный паттерн для проектов, желающих сохранить возможность переключения
try:
from PySide6.QtWidgets import QApplication
except ImportError:
from PyQt6.QtWidgets import QApplication
Пояснения к коду
Сравнение импортов показывает, что подавляющая часть кода переносится между библиотеками почти без изменений — однако Signal/Slot в PyQt6 называются pyqtSignal/pyqtSlot, что является одним из немногих, но заметных синтаксических расхождений, требующих либо прямого переименования при импорте (как в примере, через as), либо явного использования библиотеко-специфичных имён. Различие в обработке перечислений (Qt.AlignCenter против обязательного Qt.AlignmentFlag.AlignCenter) показывает более тонкую, менее очевидную область расхождения, способную привести к ошибкам при попытке использовать код, написанный для одной библиотеки, без изменений в другой. Паттерн с try/except ImportError показывает практичный, хотя и не всегда необходимый подход к написанию кода, минимально зависящего от конкретного выбора между двумя библиотеками — полезный, в частности, для библиотек/инструментов, которые сами не диктуют выбор конкретной Qt-обёртки своим пользователям.
Подводные камни
- Смешивание PySide6 и PyQt6 в рамках одного процесса/приложения — хотя обе библиотеки оборачивают одни и те же базовые библиотеки Qt, они не предназначены для одновременного использования в одном приложении, и попытка смешать объекты, созданные через разные обёртки (например, передать объект PySide6 в функцию, ожидающую объект PyQt6), может привести к неожиданным ошибкам совместимости типов — проект должен явно выбрать одну из двух библиотек и придерживаться этого выбора во всём своём коде.
- Игнорирование точных условий LGPL при использовании PySide6 в коммерческом, закрытом приложении — несмотря на то, что LGPL значительно более permissive по сравнению с GPL для случая PyQt6, LGPL всё же накладывает определённые требования (в частности, связанные с возможностью замены используемой библиотеки конечным пользователем при динамической линковке), и перед использованием в реальном, распространяемом коммерческом продукте стоит внимательно изучить точные условия применимой версии лицензии, а не предполагать полную свободу использования без каких-либо ограничений вовсе.
- Выбор PyQt6 для коммерческого, закрытого проекта без оценки стоимости и условий необходимой коммерческой лицензии — если использование PyQt6 в рамках GPL неприемлемо для конкретного коммерческого, не раскрывающего исходный код проекта, потребуется покупка отдельной коммерческой лицензии у Riverbank Computing, что является дополнительной статьёй расходов и юридическим обязательством, которое стоит explicitly оценить и учесть в планировании проекта на самом раннем этапе, а не обнаруживать эту необходимость уже на стадии готовности к коммерческому распространению.
- Предположение о полной идентичности всех мельчайших деталей API между PySide6 и PyQt6 без проверки конкретных, используемых в проекте классов — хотя общий, основной API действительно практически идентичен, отдельные, менее распространённые классы или недавно добавленные возможности могут иметь различия в точности соответствия одной из двух библиотек оригинальному C++ API на конкретный момент времени, и для специфичных, не самых распространённых случаев стоит явно проверить актуальную документацию используемой версии конкретной библиотеки.