When creating nested subclasses of QObjects using PySide, signal-slot connections fail to create in cases where parent and child classes have the same name.
Suppose we create a subclass of QTreeView named MyTreeView in module01.py, and add QSignals to its class definition. Then, in a different module02.py, we create a subclass of module01.MyTreeView and also name it MyTreeView, adding further QSignals to its class definition. At runtime, if we attempt to connect to the inherited QSignals, the connection fails with:
Sort Warning
Signals and slots in QMetaObject '{objectName}' are not ordered correctly, this may lead to issues.
If the most-nested class is renamed to anything else (MyTreeView2 etc) everything is working as intended. The issue only occurs when the parent and child have the same name. Also if they are separated by additional intermediate classes everything is OK as well. The issue only occurs when the immediate parent has the same name as its immediate child.
We looked for existing PySide bugs reported with the Sort Warning error, and found PYSIDE-2033, but it seems to be a different issue. We're also aware that the same warning can be elicited with combinations of dynamically-added signals and slots, particularly where the @QtCore.Slot() decorator is not used. However, this is a separate issue again.
This is a long-running issue in PySide--we have tested on both PySide 6.x and PySide 2.x and the issue is consistently generated. Interesting, we have no such issue with PyQt (we are migrating an existing codebase from PyQt to PySide).
As a workaround we can ensure that the class names differ where possible. However, this is not always desirable. In some cases the intermediate class name is fixed via a third-party library and we want to reuse the same class name in our own child classes rather than needing a contrived prefix or suffix to uniquify it. (Side note: In C++ it is not possible to use the same class name for parent and child classes unless they are in different namespaces; in Python a similar rule applies except that we can regard a module as a namespace, so for example we could create, say, class Matrix(math.Matrix)... in cases where we want to create a subclass but reuse the same name from a different module if it is the most natural name for the given problem domain).
Inspecting the code, it seems that this issue emerged as a by-product of the fix for PYSIDE-315 from April 2016, where signal-slot sorting was added. It seems that MetaObjectBuilderPrivate::parsePythonType() is involved. There is a potentially interesting line of code where parent-child class names are used to determine whether or not to follow a specific code-branch. I suspect something like this is happening here.
To reproduce:
pyside1.py
# Create a subclass of QTreeView named MyTreeView with two signals. from PySide6 import QtCore, QtWidgets class MyTreeView(QtWidgets.QTreeView): signal01 = QtCore.Signal() signal02 = QtCore.Signal() def __init__(self, parent=None): super(MyTreeView, self).__init__(parent=parent)
pyside2.py
from PySide6 import QtCore, QtWidgets import pyside1 # Create a subclass of pyside1.MyTreeView, also called MyTreeView. # In the initialiser, attempt to create signal-slot connections to # both inherited class signals and current class signals. class MyTreeView(pyside1.MyTreeView): signal03 = QtCore.Signal() def __init__(self, parent=None): super(MyTreeView, self).__init__(parent=parent) self.signal01.connect(self.slot01) self.signal02.connect(self.slot02) self.signal03.connect(self.slot03) @QtCore.Slot() def slot01(self): print("slot01 invoked...") @QtCore.Slot() def slot02(self): print("slot02 invoked...") @QtCore.Slot() def slot03(self): print("slot03 invoked...") # Create a placeholder main window to house an instance of MyTreeView. # When the window is shown, invoke various signals from MyTreeView to check # whether the slots are currectly invoked. class MainWindow(QtWidgets.QMainWindow): def __init__(self): super(MainWindow, self).__init__() self._control = MyTreeView() self.setCentralWidget(self._control) self.show() self._control.signal01.emit() self._control.signal02.emit() self._control.signal03.emit() if __name__ == "__main__": # Launch the main window. import sys app = QtWidgets.QApplication(sys.argv) window = MainWindow() sys.exit(app.exec())
At a bare minimum, if this is a hard-and-fast limitation of PySide6 that is unlikely to be addressed in the near future, documenting this limitation and including a note regarding this issue in the Sort Warning would help save developer time.
- relates to
-
PYSIDE-315 Using signals derived from Mixin breaks slots in derived classes
-
- Closed
-
-
PYSIDE-2033 Signals are not ordered correctly with multiple inheritance
-
- Closed
-
-
PYSIDE-463 Document PYSIDE-315 implications
-
- Open
-
For Gerrit Dashboard: PYSIDE-3201 | ||||||
---|---|---|---|---|---|---|
# | Subject | Branch | Project | Status | CR | V |
680724,6 | Fix handling type with equal names in signal/slot | dev | pyside/pyside-setup | Status: MERGED | +2 | +1 |
681245,2 | Fix handling type with equal names in signal/slot | 6.10 | pyside/pyside-setup | Status: MERGED | +2 | 0 |