是否可以在不使用类变量的情况下在运行时在实例上创建 pyqtSignals?

Posted

技术标签:

【中文标题】是否可以在不使用类变量的情况下在运行时在实例上创建 pyqtSignals?【英文标题】:Is it possible to create pyqtSignals on instances at runtime without using class variables? 【发布时间】:2018-10-22 00:12:13 【问题描述】:

是否有可能在需要时在运行时创建信号?

我在一个函数中做这样的事情:

class WSBaseConnector(QObject)

    def __init__(self) -> None:
        super(QObject, self).__init__()    
        self._orderBookListeners: Dict[str, pyqtSignal[OrderBookData]] = 

    def registerOrderBookListener(self, market: str, listener: Callable[[OrderBookData], None], loop: AbstractEventLoop) -> None:
            try:
                signal = self._orderBookListeners[market]
            except KeyError:
                signal = pyqtSignal(OrderBookData)
                signal.connect(listener)
                self._orderBookListeners[market] = signal
            else:
                signal.connect(listener)

如您所见,我有一个存储 str、pyqtSignal 对的字典。当我尝试将信号连接到侦听器时,出现错误:

'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect'

没有class vars就不能在运行时创建pyqtSignals吗?

干杯。

【问题讨论】:

【参考方案1】:

不,这是不可能的。 pyqtSignal object 是一个工厂函数,它返回一个descriptor,因此必须在执行类语句时创建它。引用文档:

新信号只能在 QObject 的子类中定义。 他们 必须是类定义的一部分,不能动态添加 作为类定义后的类属性

以这种方式定义的新信号将自动添加到 类的 QMetaObject。这意味着它们将出现在 Qt Designer 中 并且可以使用 QMetaObject API 进行自省。 [强调]

您的代码正在创建 unbound 信号对象,这就是您收到属性错误的原因。绑定和未绑定信号之间的区别与类的方法完全相同。再次引用文档:

信号(特别是未绑定信号)是一个类属性。当一个 信号被引用为类实例的属性然后 PyQt5 自动将实例绑定到信号,以便 创建一个绑定信号。这与 Python 本身的机制相同 用于从类函数创建绑定方法。

【讨论】:

【参考方案2】:

据我所知,@ekhumoro 接受的答案是完全准确的(特别是声明:“它们必须是类定义的一部分,并且在定义类后不能作为类属性动态添加。")。

不幸的是,我看到这个答案被误解为“不可能以编程方式生成 Qt 信号”,当然,对于 Python,这是一个完全不同的命题。

所以我担心这个答案解决了最初的问题,它真的想在运行时添加信号,相反我想我会直截了当并提供一个以编程方式创建信号的示例,而不与上述陈述相矛盾。解决方案是创建一个动态生成的类以及一些动态生成的信号。在 Python 中有几种动态生成类的好方法:

选项 1 - 使用类型函数

from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtCore import QMetaObject


DynamicSignal = type("DynamicSignal", (QObject, ), 
    "my_custom_signal": pyqtSignal([str]),
)


if __name__ == '__main__':
    dynamic = DynamicSignal()
    dynamic.my_custom_signal.connect(print)

    dynamic.my_custom_signal.emit("Hello world")

这会在执行时打印“Hello world”。

选项 2 - 使用元类

我们也可以通过元类来实现上述目标:

from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtCore import QMetaObject


class DynamicSignalMeta(type(QObject)):
    def __new__(cls, name, bases, dct):
        dct['my_custom_signal'] = pyqtSignal([str])
        return super().__new__(cls, name, bases, dct)


class DynamicSignal(QObject, metaclass=DynamicSignalMeta):
    pass


if __name__ == '__main__':
    dynamic = DynamicSignal()
    dynamic.my_custom_signal.connect(print)

    dynamic.my_custom_signal.emit("Hello world")

【讨论】:

这不会动态创建任何信号:它只是动态创建(恰好包括pyqt信号)。 OP 正在询问如何在运行时将 pyqt 信号添加到 预先存在的类实例 我认为我在答案开头的警告将适当的上下文放在适当的位置。我不认为我们的任何一个答案都是不正确的,我绝不会争辩说可以直接动态发出信号,但我看到你的回答过去常常争辩说不可能动态发出任何信号(甚至间接)。你的精确措辞的后一种扩展当然是完全不准确的——因为你只需要创建一个具有适当定义的 class 。这些答案的目的是平衡错误的解释。 好吧,关于这些“不正确的解释”,我不得不相信你的话,因为你没有提供任何直接引用。无论如何,我已经编辑了这个问题以避免任何进一步的疑问。【参考方案3】:

在我的other answer 中,我关注的问题是“是否可以以编程方式添加信号”,而不是 OP 所问的“是否可以在运行时动态添加信号(即在类已被实例化)”。

与@ekhumoro's accepted answer相反,我认为实际上可以在运行时添加信号,尽管 PyQT 文档的声明非常明确:

它们必须是类定义的一部分,不能在类定义后作为类属性动态添加

虽然我不怀疑该陈述的准确性,但 Python 是一种极好的动态语言,实际上它相当容易达到预期的结果。我们必须克服的问题是,为了在运行时添加信号,我们必须创建一个新的类定义并修改实例的底层类。在 Python 中,这可以通过设置对象的 __class__ 属性(通常为 has a number of issues to be aware of)来实现。

from PyQt5.QtCore import QObject, pyqtSignal


class FunkyDynamicSignals(QObject):
    def add_signal(self, name, *args):
        # Get the class of this instance.
        cls = self.__class__

        # Create a new class which is identical to this one,
        # but which has a new pyqtSignal class attribute called of the given name.
        new_cls = type(
            cls.__name__, cls.__bases__,
            **cls.__dict__, name: pyqtSignal(*args),
        )
        # Update this instance's class with the newly created one.
        self.__class__ = new_cls  # noqa

通过这个类,我们可以在对象被实例化后创建信号:

>>> dynamic = FunkyDynamicSignals()
>>> dynamic.add_signal('example', [str])
>>> dynamic.example.connect(print)

>>> dynamic.add_signal('another_example', [str])
>>> dynamic.another_example.connect(print)

>>> dynamic.example.emit("Hello world")
Hello world

这种方法使用现代 Python 语法(但同样可以为 Py2 编写),小心地公开合理的类层次结构,并在添加新信号时保留现有连接。

【讨论】:

注意:我没有尝试将信号绑定到一个类的单个实例(即在另一个实例中构造示例之后阻止FunkyDynamicSignals().example 的使用。不过,使用 Python 一切皆有可能。 .. :) 再一次,这是动态创建而不是信号。 OP 特别询问是否有可能“在运行时创建 pyqtSignals 而不是类变量”。 我绝不会声称可以动态创建信号。我在回答中要指出的是,从用户/API 的角度来看,在不手动声明类变量的情况下创建信号是完全可行的。此答案中的示例显示了一种这样的方法-从用户的角度来看,您只需调用一个方法,它就会添加一个信号,用户在任何时候都不知道他们实际上是在创建一个新类并变形实例。我不太喜欢这个答案中的代码(以我的方式更改课程很难看),但它确实回答了 IMO 的问题。 这就是你这样做的地方:“与@ekhumoro 接受的答案相反,我认为实际上可以在运行时添加信号”。我的回答是专门关于在运行时向 instances 添加信号,因为这是 OP 所要求的(另请参阅他们的代码和错误消息)。您的答案与更一般的主题有关,绝不与我的“相反”。事实上,我不明白为什么有必要在你的答案中引用我的答案。

以上是关于是否可以在不使用类变量的情况下在运行时在实例上创建 pyqtSignals?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以在不实际部署映像的情况下在 GCE 上配置容器优化的 OS VM?

如何在不使用 Segue 的情况下在单独的 UIViewController 中选择实例变量

是否可以在不重新启动服务器的情况下在远程 weblogic 上重新部署应用程序?

是否可以在不做任何更改的情况下在 Android 手机中运行 JavaFX 桌面应用程序? [复制]

是否可以在不使用整个框架的情况下在 PHP 中为 ORM 安装 Kohana 库?

如何在不创建新的表单实例的情况下在表单之间切换?