Введение
QML-цикл (статьи 479-498) разбирал интеграцию C++ и QML — в этой статье разберём тот же набор концепций (контекстные свойства, регистрация типов) применительно к Python, что особенно ценно для разработчиков, предпочитающих современный, декларативный Qt Quick UI, но желающих писать логику приложения на Python, а не на C++.
Концепция
QQmlApplicationEngine используется из Python синтаксически почти идентично C++ версии (статья 490 QML-цикла) — загрузка QML-файла через engine.load(), передача Python-объектов в QML через engine.rootContext().setContextProperty(), и регистрация Python-классов как QML-типов через декоратор @QmlElement (более современный, декларативный подход) или функцию qmlRegisterType() (аналогичную одноимённой C++ функции).
Пример кода: QML из Python
# Базовая загрузка QML из Python
import sys
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.load("main.qml")
if not engine.rootObjects():
sys.exit(-1) # явная проверка, что QML действительно успешно загрузился
sys.exit(app.exec())
# Передача Python-объекта в QML через контекстное свойство (аналогично статье 490 QML-цикла)
from PySide6.QtCore import QObject, Property, Signal
class AppSettings(QObject):
usernameChanged = Signal(str)
def __init__(self):
super().__init__()
self._username = "Гость"
def getUsername(self) -> str:
return self._username
def setUsername(self, value: str):
if self._username != value:
self._username = value
self.usernameChanged.emit(value)
username = Property(str, getUsername, setUsername, notify=usernameChanged)
settings = AppSettings()
engine.rootContext().setContextProperty("appSettings", settings)
# В QML теперь доступно: Text { text: appSettings.username }
# Регистрация Python-класса как QML-типа через декоратор QmlElement (современный, декларативный подход)
from PySide6.QtQml import QmlElement, QmlSingleton
QML_IMPORT_NAME = "MyApp.Models"
QML_IMPORT_MAJOR_VERSION = 1
@QmlElement
class TaskItem(QObject):
# ... объявление свойств через Property, аналогично примеру AppSettings выше ...
pass
# После регистрации в QML: import MyApp.Models; TaskItem { ... }
Пояснения к коду
Базовый пример загрузки QML показывает, что структура Python-кода для запуска QML-приложения почти полностью идентична C++ версии — основное синтаксическое отличие лишь в обычных питонических деталях (явный import, отсутствие точки с запятой), а сама логика (создание engine, загрузка файла, проверка rootObjects(), запуск exec()) совпадает по смыслу буквально построчно. Передача settings через setContextProperty("appSettings", settings) показывает практически идентичный паттерн контекстного свойства из статьи 490 QML-цикла — AppSettings, реализованный через Property/Signal (статья 502), становится доступен в QML точно так же, как и его C++ эквивалент, демонстрируя реальную взаимозаменяемость C++ и Python для этой части архитектуры Qt-приложения. Декоратор @QmlElement с предварительным объявлением QML_IMPORT_NAME/QML_IMPORT_MAJOR_VERSION показывает более современный, декларативный способ регистрации типа по сравнению с явным вызовом функции qmlRegisterType(), концептуально аналогичный системе QML_ELEMENT-макросов для C++-кода в современных версиях Qt.
Подводные камни
- Забытая проверка
engine.rootObjects()послеload(), что может скрыть факт неудачной загрузки QML-файла (синтаксическая ошибка в QML, отсутствующий файл) — без этой явной проверки приложение может просто завершиться без какого-либо видимого окна и без понятного сообщения об ошибке, что усложняет диагностику проблемы по сравнению с явной, информативной обработкой этого случая, как показано в базовом примере. - Преждевременное удаление Python-объекта, переданного как контекстное свойство, сборщиком мусора Python, аналогично подводному камню статьи 500 про виджеты — если единственная ссылка на
settingsбыла локальной переменной функции, вышедшей из области видимости после вызоваsetContextProperty, сборщик мусора Python может удалить этот объект, оставив QML-сцену с «висячей» ссылкой на уже несуществующий объект; объект должен сохраняться в переменной с достаточно долгим временем жизни (например, атрибутом самого приложения или класса-обёртки) на протяжении всего времени, пока QML-сцена может к нему обращаться. - Путаница между
@QmlElement(более современный подход, требующий определённой минимальной версии PySide6) и более старымqmlRegisterType()— для проектов, использующих более старые версии PySide6, декоратор@QmlElementможет быть недоступен, и в таком случае нужно использовать явный вызовqmlRegisterType(); смешивание обоих подходов без понимания их функциональной эквивалентности может создать путаницу при чтении и поддержке кода. - Игнорирование того, что Python и QML работают в рамках одного, общего событийного цикла Qt, и долгая, блокирующая операция в Python-коде, вызванном из QML (через
Slot, статья 501), полностью блокирует также и весь QML-интерфейс на время этой операции — аналогично подводным камням блокирующих операций в основном цикле статей про многопоточность, длительные операции должны выполняться в отдельном потоке (статья 505) или асинхронно, а не блокировать основной поток, общий для и Python-логики, и отображения QML-интерфейса.