PySide6.QtQmlFeatures¶
Note
Qt QML Features in 6.12 is in Technology Preview, excluding its API from Qt’s compatibility promises (see Qt Releases).
The Qt QML Features module provides a small reactive property system for
QObject subclasses. It lets you observe property
changes, derive values that recompute automatically, and run side effects
when a property changes, all from plain Python code.
The building block is the Change type, which describes a single
property change. On top of it the module provides a set of method and class
decorators: @watch, @computed, @effect, and @auto_properties.
To use the module, import the names you need from it:
from PySide6.QtQmlFeatures import Change, watch, computed, effect
from PySide6.QtQmlFeatures import load_qml_component
The Change type¶
A Change is an immutable record of one property change. It is the
object passed to every @watch callback and carries the property name, the
value before the change, the value after the change, and the object that owns
the property.
- class Change(name, old, new, owner)¶
- Parameters:
name (str) – the name of the property that changed.
old (object) – the value the property held before the change.
new (object) – the value the property holds after the change.
owner (object) – the object whose property changed.
The constructor arguments are available as the read-only attributes
name,old,new, andowner.
For example, creating a change for a price property that went from 10
to 20 looks like this:
from PySide6.QtQmlFeatures import Change
change = Change(name="price", old=10, new=20, owner=cart)
print(change.name, change.old, change.new) # price 10 20
You normally do not construct Change objects yourself; the reactive
property system creates one and hands it to your @watch callbacks whenever
an observed property changes.
The @watch decorator¶
@watch marks a method as an observer of a single property. When that
property changes, the method is called with a Change describing what
happened. The decorated method must accept the change object in addition to
self.
- @watch(property_name)¶
- Parameters:
property_name (str) – the name of the property to observe.
Returns a decorator that registers the method as a watcher of property_name. The method is invoked as
method(self, change)after the property changes, where change is aChangeinstance.
The observers are wired up by the @auto_properties class decorator;
@watch on its own only records the property name on the method.
Register the type with @QmlElement so QML can instantiate it and write
price; the @watch callback then runs on the Python side.
Python:
from PySide6.QtCore import QObject
from PySide6.QtQml import QmlElement
from PySide6.QtQmlFeatures import watch, Change, auto_properties
QML_IMPORT_NAME = "Shop"
QML_IMPORT_MAJOR_VERSION = 1
@QmlElement
@auto_properties
class Cart(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self.price = 10
@watch("price")
def on_price_changed(self, change: Change):
print(f"{change.name}: {change.old} -> {change.new}")
QML:
Cart {
id: cart
// Clicking writes 'price' from QML; the @watch callback then runs
// on the Python side, printing "price: 10 -> 11".
Button { onClicked: cart.price += 1 }
}
A @watch callback is only invoked when the value actually changes; setting
a property to its current value does not trigger it.
The @computed decorator¶
@computed turns a method into a derived, read-only property. The method
computes a value from one or more other properties, named as the decorator’s
arguments. The result is cached and recomputed only when one of those
dependencies changes.
- @computed(*dep_names)¶
- Parameters:
dep_names (str) – the names of the properties this value depends on. At least one name is required.
Returns a decorator that registers the method as a computed property whose value depends on dep_names. Reading the property returns the cached value, recomputing it when any dependency has changed since the last read.
Like @watch, @computed records its metadata on the method; the
dependency tracking is wired up by the @auto_properties class decorator.
When a dependency changes, the computed property’s Changed signal is
emitted automatically. This means QML bindings that read the computed
property are notified and re-evaluated just like any other Qt property.
Python:
@QmlElement
@auto_properties
class Cart(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self.price = 10
self.quantity = 2
@computed("price", "quantity")
def total(self):
return self.price * self.quantity
QML:
Cart { id: cart }
// Automatically re-evaluates whenever price or quantity changes
Text { text: "Total: " + cart.total }
Button { onClicked: cart.price += 1 }
The @effect decorator¶
@effect marks a method as a side effect that runs whenever any of the
named properties changes. Unlike @watch, an effect does not receive a
Change; it is simply called with self so it can react to the new
state, for example by updating a widget or writing a log entry.
- @effect(*property_names)¶
- Parameters:
property_names (str) – the names of the properties that trigger this effect. At least one name is required.
Returns a decorator that registers the method as an effect of property_names. The method is invoked as
method(self)after any of those properties changes.
The effect is wired up by the @auto_properties class decorator.
Register the type with @QmlElement so QML can instantiate it and write
value; the @effect method then runs on the Python side.
Python:
from PySide6.QtCore import QObject
from PySide6.QtQml import QmlElement
from PySide6.QtQmlFeatures import effect, auto_properties
QML_IMPORT_NAME = "Shop"
QML_IMPORT_MAJOR_VERSION = 1
@QmlElement
@auto_properties
class Item(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self.value = 0
@effect("value")
def on_value_changed(self):
print(f"value is now {self.value}")
QML:
Item {
id: item
// Each slider move writes 'value' from QML; the @effect method
// then runs on the Python side, printing "value is now <n>".
Slider { onValueChanged: item.value = value }
}
An effect that lists several properties runs once for each change to any of them.
Comparison: @watch vs @effect¶
|
|
|
|---|---|---|
Properties watched |
One per decorator (stack to watch more) |
Many, listed in a single decorator call |
Method signature |
|
|
Receives old/new values |
Yes — via |
No |
Use @watch when you need the before/after values; use @effect when you
only need to react to the new state, for example to persist settings or update
a derived value that does not need Change details.
The @auto_properties decorator¶
@auto_properties is a class decorator that ties everything together. It
makes a QObject subclass fully reactive and
QML bindable: every reactive member becomes a real
Property (a Q_PROPERTY) with a change-notification
signal, and every @watch, @computed, and @effect observer is wired
so its callback runs as a side effect of a property change, including a
change written from QML.
- @auto_properties¶
Class decorator for a
QObjectsubclass. Returns the same class, augmented in place. RaisesTypeErrorif applied to a class that is not derived fromQObject.
It recognises three sources of reactive state on the class:
Plain assignments in
__init__. For everyself.<name> = <value>it builds a property<name>backed by a private attribute, together with a<name>Changedsignal.Native Python
@propertydeclarations are converted into aProperty, reusing your getter and setter and adding a<name>Changedsignal. A getter-only@propertybecomes a read-only property: writing it (including from QML) raises, and no signal is created.Existing
Propertydeclarations are left exactly as you declared them. Their setter is wrapped only when a@watch,@effect, or@computedrefers to the property, so the observer callbacks run on change; the notify signal stays the one you declared.
The Qt type of a generated or converted property is inferred so that QML sees
a precise type wherever possible: built-in Python types map to their Qt
counterparts (int, float -> double, bool, str ->
QString), list and dict become QVariantList and QVariantMap,
and anything else falls back to QVariant. For an __init__ attribute the
type comes from the assigned default value; for a @property or @computed
it comes from the getter’s return annotation.
Attribute names starting with an underscore are left untouched, and a name that
is explicitly declared as a property always wins over an __init__ guess of
the same name. Applying @auto_properties twice to the same class is a no-op.
With @auto_properties you write ordinary attribute assignments on a type
registered with @QmlElement, and QML can both read and write them, with the
observers firing on every change. Stack the decorators so @auto_properties
runs first (innermost): it must add the generated Q_PROPERTY objects to the
QMetaObject before @QmlElement registers the type with the QML engine.
Python:
from PySide6.QtCore import QObject
from PySide6.QtQml import QmlElement
from PySide6.QtQmlFeatures import auto_properties, watch, computed, Change
QML_IMPORT_NAME = "Shop"
QML_IMPORT_MAJOR_VERSION = 1
@QmlElement
@auto_properties
class Cart(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self.price = 10
self.quantity = 2
@computed("price", "quantity")
def total(self):
return self.price * self.quantity
@watch("price")
def on_price(self, change: Change):
print(f"price changed to {change.new}")
QML:
import Shop
Cart {
id: cart
// Reads the generated 'price'/'quantity' properties and the
// @computed 'total'; re-evaluates whenever any of them changes.
Text { text: "Total: " + cart.total }
// Writing 'price' from QML runs the generated setter, which fires
// the @watch callback, invalidates 'total', and emits priceChanged.
Button { onClicked: cart.price += 1 }
}
Because price and quantity are real Q_PROPERTY objects with notify
signals, the QML binding above set on cart.price runs the generated setter,
which fires the @watch callback, invalidates total, and emits
priceChanged so any QML binding reading the value refreshes.
Loading QML components from Python¶
load_qml_component loads a QML component - a custom .qml file or a type
from a QML module so it can be instantiated and driven entirely from
Python, with no QML glue code.
- class load_qml_component(engine, source=None, *, module=None, type_name=None)¶
Loads a QML component and returns a factory for it. Pass a
QQmlEngine(orQQmlApplicationEngine) as the first argument. Supply exactly one ofsource(a path to a.qmlfile, absolute or relative to the calling.pyfile) or bothmodule(a QML module URI such as"QtQuick.Controls") andtype_name(a type within it, such as"Slider"). The returned object is a factory whosecreate()builds instances.
The factory is lightweight. The actual QML loading and object creation
happens in create(**initial_properties), which returns a wrapper whose
QML properties, signals, and methods are exposed as plain Python
attributes:
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtQmlFeatures import load_qml_component
engine = QQmlApplicationEngine()
Person = load_qml_component(engine, "person.qml")
alice = Person.create(name="Alice", age=28)
alice.age = 29 # write a QML property
print(alice.name) # read a QML property
alice.birthdayHappened.connect(...) # connect to a QML signal
alice.celebrateBirthday() # call a QML method
raw = alice.qobject # underlying QObject escape hatch
Assigning a wrapper to a QML property that expects a QObject (for
example a parent item) unwraps it automatically.
Note
Calling a QML method currently returns a success flag rather than the method’s return value.
See the example_qml_qmlcomponentloading example for a full, runnable program.