我们能在PySide中发射基类的信号吗?

5

是否可以从基类继承信号,并在派生类中连接方法?如果是,如何实现?

使用组合的工作测试案例

MyWidget中实例化一个MyObject,并在小部件中对由对象发出的信号做出反应。

from PySide.QtGui import QApplication, QMainWindow
from PySide.QtCore import QObject, QTimer, Signal
from PySide.QtGui import QLabel

class MyObject(QObject):
    sig = Signal()

    def __init__(self, parent=None):
        super().__init__(parent)
        QTimer.singleShot(3000, self.alert)
        QTimer.singleShot(5000, self.exit)
    def alert(self):
        self.sig.emit()
    def exit(self):
        print('All done')
        exit(0)

class MyWidget(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.monitor = MyObject(self)
        self.monitor.sig.connect(self.update)
    def update(self):
        print(2)

app = QApplication([])
w = MyWidget()
w.show()
app.exec_()

这是一个小而实用的示例,它打开了一个最小的、空白的窗口,由控件实例化的 self.monitor 对象在 3 秒和 5 秒后发出计时器信号。第一个信号提示控件将一个数字打印到控制台,第二个信号导致应用程序退出。

继承失败的测试用例

对于继承,只需更改控件类:
class MyWidget(MyObject, QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.sig.connect(self.update)
    def update(self):
        print(2)

如果在控制台中运行此代码,将不会打印任何输出,但会出现分段错误。为什么?这个问题能解决吗?

通过替换 super() 来修复

有趣的是,如果两个类都改为不使用 super(),则示例代码将再次正常工作:

class MyObject(QObject):
    sig = Signal()

    def __init__(self, parent=None):
        QObject.__init__(self, parent)
        QTimer.singleShot(3000, self.alert)
        QTimer.singleShot(5000, self.exit)
    def alert(self):
        self.sig.emit()
    def exit(self):
        print('All done')
        exit(0)

class MyWidget(MyObject, QLabel):
    def __init__(self, parent=None):
        MyObject.__init__(self, parent)
        QLabel.__init__(self, parent)
        self.sig.connect(self.update)
    def update(self):
        print(2)

通常我更喜欢使用super(),但也许我需要重新考虑一下?我特意链接了两篇关于在Python中使用super()的有争议的文章。现在我对如何在pyside中正确使用super()或者为什么它根本不起作用感兴趣。

小更新: 当使用继承和super(),但删除所有与信号相关的代码时,示例可以正常工作,即打开窗口并且不会崩溃。因此,似乎存在一些迹象表明super()初始化和信号的组合会导致问题。

小更新2: ..并且当注释掉self.sig.connect时,窗口启动并且只有在5秒信号触发退出应用程序时才会崩溃。

(这是在Ubuntu 13.04系统上使用Qt 4.8.4,Pyside 1.1.2和CPython 3.3.1解释器)


它会崩溃还是干净退出?我认为sig.connect()就足够了,因为信号应该是类级别而不是实例化级别属性。 - tacaswell
@tcaswell:按照我的定义,段错误是指崩溃和没有干净的退出。信号始终在对象之间连接。这份文档没有明确说明,但没有列出其他选择。思考一下,将信号和槽连接到类中是没有意义的,因为这将意味着这些类的所有对象都已连接。实例可以连接并相互通信,而类只是在这方面的typedefs。类型可以定义如何相互通信的协议,但它们实际上无法进行通信。对象可以。 - cfi
你之前没有明确提到你遇到了段错误。封装层正在进行深度魔法,将Python映射到C++并返回。如果你遇到了段错误,那么你可能会将C++层推入一个糟糕的状态。 - tacaswell
仔细观察后,我怀疑问题出在C++层中出现了钻石图。尽管Python可以处理它,但是pyside对象只是C++对象的薄包装器,它们的实例化被搞砸了。我对super的理解是,它就像C++中的const正确性一样,如果你在每个地方都使用它,它会非常好用,但如果你只有时候使用它,它会带来很大的痛苦。 - tacaswell
那就是,我认为如果你不混合使用 QLabel,它会正常工作。 - tacaswell
显示剩余2条评论
2个回答

4

Qt不支持QObjects进行多重继承, PySide和PyQt同样存在这种限制。虽然有时有办法绕过这个限制,但通常试图创建具有两个或更多QObject基类的子类是一个不好的想法。

对于信号的继承,使用一个简单的非QObject混入可能是最好的方法 - 尽管我认为这个解决方案仅适用于PySide;对于PyQt4,信号只能在QObject子类上定义。

更新

这个限制已经在PyQt5中被取消:现在可以在不继承QObject的类中定义属性、信号和槽了。


啊,PyQt的这个小技巧真是有趣!我很高兴从pyside中获得了这个小优势。 - cfi

1

目前我能想到的唯一满足(a)继承和(b)使用super()的解决方案,或者说是权宜之计,是受用户tcaswell评论启发,避免钻石关系。

对于我的用例来说,保留任何消费者类(用于自定义小部件)的继承非常重要。另一方面,当前保证所有消费类都将(间接地)派生自QObject。因此,不需要将MyObjectQObject派生出来,尽管这实际上创建了一个真正的抽象MixIn类:它不能独立使用,并且具有对消费类的接口要求。我不确定是否喜欢它。

这里是可工作的代码:

from PySide.QtGui import QApplication, QMainWindow
from PySide.QtCore import QObject, QTimer, Signal
from PySide.QtGui import QLabel

class MyObject:
    sig = Signal()

    def __init__(self, parent=None):
        super().__init__(parent)
        QTimer.singleShot(3000, self.alert)
        QTimer.singleShot(5000, self.exit)
    def alert(self):
        self.sig.emit()
    def exit(self):
        print('All done')
        exit(0)

class MyWidget(MyObject, QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.sig.connect(self.update)
    def update(self):
        print(2)


app = QApplication([])

w = MyWidget()
w.show()


app.exec_()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接