为什么从 QListWidgetItem 和 QObject 派生时崩溃

Why crash when derive from QListWidgetItem AND QObject

以下最小示例在 windows 上的 pyqt 5.7.1 中崩溃(将其复制粘贴到 .py 文件中并 运行):

from PyQt5.QtWidgets import QListWidgetItem, QListWidget, QApplication
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal


class MyListItem(QListWidgetItem):
    def __init__(self, obj):
        QListWidgetItem.__init__(self, 'example')
        obj.sig_name_changed.connect(self.__on_list_item_name_changed)

    def __on_list_item_name_changed(self, new_name: str):
        self.setText(new_name)


class MyListItem2(QListWidgetItem, QObject):
    def __init__(self, obj):
        QListWidgetItem.__init__(self, 'example')
        QObject.__init__(self)
        obj.sig_name_changed.connect(self.pyqt_slot)

    @pyqtSlot(str)
    def __on_list_item_name_changed(self, new_name: str):
        self.setText(new_name)


class Data(QObject):
    sig_name_changed = pyqtSignal(str)


class SearchPanel(QListWidget):
    def __init__(self, parent=None):
        QListWidget.__init__(self, parent)
        obj = Data()
        hit_item = MyListItem(obj)  # OK
        hit_item = MyListItem2(obj)  # crashes
        self.addItem(hit_item)
        obj.sig_name_changed.emit('new_example')


app = QApplication([])
search = SearchPanel()
search.show()
app.exec()

现在只需注释掉 "crashes" 行,就可以正常工作了。此外,列表小部件显示 'new_example',表明信号已通过。

有没有办法让它与 MyListItem2 一起使用?即我希望能够用 pyqtSlot 装饰插槽,这反过来要求(在 PyQt 5.7 中)我从 QObject 派生项目。

这里的目的是列表中的每个项目都有几个可以根据来自相关数据实例的信号改变的特征(图标、字体、文本颜色)(在 Qt 意义上每个实例实际上 "lives"术语,在我们应用程序的第二个线程中)。

这与pyqtSlot无关。

实际问题是您试图继承两个 Qt classes,即 not generally supported。唯一的例外是实现接口的 Qt classes 和共享一个公共基 class 的 Qt classes(例如 QListWidgetQWidget) .但是,官方 只支持前者,关于后者有几个附带条件(none 其中与此处相关)。

因此从 QListWidgetItemQObject 继承的 Python class 将无法工作。主要问题发生在 PyQt 尝试访问顶级 base-class 未定义的属性时(即使该属性不存在)。在早期的 PyQt 版本中,这只会引发错误:

>>> class MyListItem2(QListWidgetItem, QObject): pass
...
>>> x = MyListItem2()
>>> x.objectName()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: could not convert 'MyListItem2' to 'QObject'
>>> x.foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: could not convert 'MyListItem2' to 'QObject'

这清楚地表明(在 C++ 术语中)a MyListItem2(QListWidgetItem) 不能转换为 QObject。不幸的是,似乎较新版本的 PyQt5 不再引发此错误,而是立即转储核心(这可能是一个错误)。

如果您确实需要使用 pyqtSlot,一个建议是使用组合而不是子classing。所以也许是这样的:

class ListItemProxy(QObject):
    def __init__(self, item, obj):
        QObject.__init__(self)
        self._item = item
        obj.sig_name_changed.connect(self.__on_list_item_name_changed)

    @pyqtSlot(str)
    def __on_list_item_name_changed(self, new_name: str):
        self._item.setText(new_name)

class MyListItem2(QListWidgetItem):
    def __init__(self, obj):
        QListWidgetItem.__init__(self, 'example')
        self._proxy = ListItemProxy(self, obj)