PySide和PyQt中Signal的工作原理
背景
PySide和PyQt能够让Qt在Python中运行。Qt的信号槽机制在PySide和PyQt中是这样使用的:
PySide:
from PySide6.QtCore import Signal, Slot, QObject
class Foo(QObject):
signal = Signal(str)
def emit_signal(self, message):
self.signal.emit(message)
class Bar(QObject):
@Slot(str)
def slot(self, message):
print(message)
f = Foo()
b = Bar()
f.signal.connect(b.slot)
f.emit_signal("Hello World!")
# 输出:
# Hello World!
PyQt:
from PyQt6.QtCore import pyqtSignal, pyqtSlot, QObject
class Foo(QObject):
signal = pyqtSignal(str)
def emit_signal(self, message):
self.signal.emit(message)
class Bar(QObject):
@pyqtSlot(str)
def slot(self, message):
print(message)
f = Foo()
b = Bar()
f.signal.connect(b.slot)
f.emit_signal("Hello World!")
# 输出:
# Hello World!
可以发现,信号定义为类的一个静态成员。但是,信号和槽的连接是在不同的对象之间进行的。所以,这到底是怎么实现的?
原理
查阅PyQt的文档,我们可以发现:
A signal (specifically an unbound signal) is a class attribute. When a signal is referenced as an attribute of an instance of the class then PyQt6 automatically binds the instance to the signal in order to create a bound signal. This is the same mechanism that Python itself uses to create bound methods from class functions.
A bound signal has connect(), disconnect() and emit() methods that implement the associated functionality. It also has a signal attribute that is the signature of the signal that would be returned by Qt’s SIGNAL() macro.
其中提到,信号定义为类的属性(类的静态成员),它是一个未绑定信号(unbound signal)。当我们用类的一个对象去访问信号时,该对象会自动绑定到信号,并创建一个绑定信号(bound signal)。我们可以做一个实验:
from PySide6.QtCore import Signal, QObject
class Foo(QObject):
signal = Signal()
signal = Signal()
print(type(signal))
f = Foo()
print(type(f.signal))
# 输出:
# <class 'PySide6.QtCore.Signal'>
# <class 'PySide6.QtCore.SignalInstance'>
可以发现,单独创建信号时,它的类型是PySide6.QtCore.Signal
,而在类中创建并且通过类的实例访问时,类型却是PySide6.QtCore.SignalInstance
。说明后者通过了某种方式创建了一个新的对象。
查阅资料发现,这其实是利用了python语言的一种机制:Descriptors。
简单来说,就是可以通过重写__get__, __set__, __delete__
函数,来实现getter/setter
。举例说明:
class MySignalInstance:
pass
class MySignal:
def __get__(self, instance, owner=None):
if (instance == None):
print('instance is None')
else:
print(f'instance is not None. type(instance): {type(instance)}')
print(owner)
return MySignalInstance()
class Foo:
mySignal = MySignal()
mySignal = MySignal()
print(type(mySignal))
f = Foo()
print(type(f.mySignal))
print(type(Foo.mySignal))
# 输出:
# <class '__main__.MySignal'>
# instance is not None. type(instance): <class '__main__.Foo'>
# <class '__main__.Foo'>
# <class '__main__.MySignalInstance'>
# instance is None
# <class '__main__.Foo'>
# <class '__main__.MySignalInstance'>
分析:
MySignal
中重写了__get__
函数,返回MySignalInstance
对象- 如果直接创建
mySignal = MySignal()
,返回的就是MySignal
的对象 - 如果在
Foo
中定义一个MySignal
的类成员变量,然后通过两种不同的方式访问:- 通过类的对象访问
f.mySignal
,此时,MySignal
的__get__
函数被调用,instance
被设为f
,owner
被设为Foo
。这意味着,在被调用方得到了调用方的对象。 - 通过类直接访问
Foo.mySignal
,此时,MySignal
的__get__
函数同样被调用,因为是通过类直接访问,所以instance
被设为None
,owner
被设为Foo
。
- 通过类的对象访问
正是通过这种机制,使用对象.信号
访问信号时,对象
被传入信号
中,接着将对象
与信号
进行绑定,返回一个绑定信号。然后就可以调用connect
和其他对象的槽进行连接。
参考
标签:__,PySide,Signal,PyQt,class,instance,print,Foo,signal From: https://www.cnblogs.com/cong-wang/p/17019992.htmlhttps://doc.qt.io/qtforpython/tutorials/basictutorial/signals_and_slots.html
https://www.riverbankcomputing.com/static/Docs/PyQt6/signals_slots.html
https://stackoverflow.com/questions/3798835/understanding-get-and-set-and-python-descriptors
https://docs.python.org/3/reference/datamodel.html#implementing-descriptors
https://docs.python.org/3/reference/datamodel.html#invoking-descriptors