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



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

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

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'

在没有类变量的情况下,就不可能在运行时创建 pyqtSignals?

干杯。

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

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

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

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

信号

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

在我的另一个答案中,我专注于"是否可以以编程方式添加信号"的问题,而不是 OP 提出的"是否可以在运行时动态添加信号(即在类实例化之后("。

与@ekhumoro公认的答案相反,我会声称实际上可以在运行时添加信号,尽管 PyQT 文档的声明非常明确:

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

虽然我不怀疑该语句的准确性,但Python是一种非常动态的语言,实际上很容易达到预期的结果。我们必须克服的问题是,为了在运行时添加信号,我们必须创建一个新的类定义并修改实例的基础类。在 Python 中,这可以通过设置对象的__class__属性来实现(这通常有许多问题需要注意(。

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 编写(,小心翼翼地公开合理的类层次结构,并在添加新信号时保留现有连接。

据我所知,

@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")

相关内容

最新更新