In PyQt, you can use QtCore.pyqtSignal()
to create custom signals.
I tried making my own implementation of the Observer pattern in place of pyqtSignal
to circumvent some of its limitations (e.g. no dynamic creation).
It works for the most part, with at least one difference.
Here is my implementation so far
class Signal:
def __init__(self):
self.__subscribers = []
def emit(self, *args, **kwargs):
for subs in self.__subscribers:
subs(*args, **kwargs)
def connect(self, func):
self.__subscribers.append(func)
def disconnect(self, func):
try:
self.__subscribers.remove(func)
except ValueError:
print('Warning: function %s not removed from signal %s'%(func,self))
The one thing noticed was a difference in how QObject.sender()
works.
I generally stay clear of sender()
, but if it works differently then so may other things.
With regular pyqtSignal
signals, the sender is always the widget closest in a chain of signals.
In the example at the bottom, you'll see two objects, ObjectA
and ObjectB
. ObjectA
forwards signals from ObjectB
and is finally received by Window.
With pyqtSignal
, the object received by sender()
is ObjectA
, which is the one forwarding the signal from ObjectB
.
With the Signal class above, the object received is instead ObjectB
, the first object in the chain.
Why is this?
Full example
# Using PyQt5 here although the same occurs with PyQt4
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Window(QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
object_a = ObjectA(self)
object_a.signal.connect(self.listen)
layout = QBoxLayout(QBoxLayout.TopToBottom, self)
layout.addWidget(object_a)
def listen(self):
print(self.sender().__class__.__name__)
class ObjectA(QWidget):
signal = Signal()
# signal = pyqtSignal()
def __init__(self, parent=None):
super(ObjectA, self).__init__(parent)
object_b = ObjectB()
object_b.signal.connect(self.signal.emit)
layout = QBoxLayout(QBoxLayout.TopToBottom, self)
layout.addWidget(object_b)
class ObjectB(QPushButton):
signal = Signal()
# signal = pyqtSignal()
def __init__(self, parent=None):
super(ObjectB, self).__init__('Push me', parent)
self.pressed.connect(self.signal.emit)
if __name__ == '__main__':
import sys
app = QApplication([])
win = Window()
win.show()
sys.exit(app.exec_())
More reference
Edit:
Apologies, I should have provided a use-case.
Here are some of the limitations of using pyqtSignals:
pyqtSignal:
Thus my main concern is using it together with baseclasses.
Consider the following.
6 different widgets of a list-type container widget share the same interface, but look and behave slightly different. A baseclass provides the basic variables and methods, along with signals.
Using pyqtSignal, you would have to first inherit your baseclass from at least QObject or QWidget.
The problem is neither of these can be use in as mix-ins or in multiple inheritance, if for instance one of the widgets also inherits from QPushButton.
class PinkListItem(QPushButton, Baseclass)
Using the Signal class above, you could instead make baseclasses without any previously inherited classes (or just object) and then use them as mix-ins to any derived subclasses.
Careful not to make the question about whether or not multiple inheritance or mix-ins are good, or of other ways to achieve the same thing. I'd love your feedback on that as well, but perhaps this isn't the place.
I would be much more interested in adding bits to the Signal class to make it work similar to what pyqtSignal produces.
Edit 2:
Just noticed a down-vote, so here comes some more use cases.
Key-word arguments when emitting.
signal.emit(5)
Could instead be written as
signal.emit(velocity=5)
Use with a Builder or with any sort of dependency injection
def create(type):
w = MyWidget()
w.my_signal = Signal()
return w
Looser coupling
I'm using both PyQt4 and PyQt5. With the Signal class above, I could produce baseclasses for both without having it depend on either.
You can do this with a metaclass that inherits from pyqtWrapperType
. Inside __new__
, call pyqtSignal()
as needed and set the attributes on the result class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With