Signals and Slots - Summerfield - what exactly is a signal?

Michael Torrie torriem at gmail.com
Sat Aug 5 13:18:15 EDT 2017


Forgive Steven for his off-topic reply. I assume he's trying to goad you
into having a more specific subject line.  He knows darn well what PyQt,
even if he has no experience with it.

And of course, as always you will want to post a complete, working
example that we can see and comment on, rather than extracts of the
code.  It's much harder to comment on that.

On 08/05/2017 08:28 AM, veek wrote:
> 1. What exactly is a signal. In hardware, an interrupt can be viewed as a 
> signal and the voltage on a pin will suddenly jump to +5V as an indicator 
> that an interrupt has occurred. With Qt signals - if a widget-c++ code has 
> to 'signal' an event - what does it do?

Basically a signal emission is a call to the main loop that indicates
that an event has occurred, and then the main loop sees if there are any
registered callbacks that want to be notified of this event, and if so
it calls them, letting them execute. This is how event-driven
programming works.  The concept of signals and slots is very similar to
explicit registering of callbacks in function. It differs only in form.

So it sounds from your post that the book you are following is older and
uses some of the older forms for signals and slots in PyQt.  In recent
years, PyQt has made some things nicer, make making signals just be
members of the class that you can access through python as if they were
a member of the instance.  so my code in response to your questions will
reflect this newer style. I recommend you look at the various online
tutorials for PyQt (I still am using PyQt4, but PyQt5 will be nearly the
same).

> As a consequence of not understanding the above..
> 
> 2. How do you connect a single signal to multiple slots? Do the 
> slots/methods get called one by one? or are they treated as independent 
> threads and run on different cores?

You simply have more than one connect() call.  for example:

button1.clicked.connect(callback1)
button1.clicked.connect(callback2)

Both callbacks will run when button1 emits its "clicked" signal.

Then the callbacks are executed in some kind of order when the signal is
emitted, one at a time.  GUIs are typically single-threaded.  That's why
it's important to not have long-running code in a callback.  If you do
need to do something in a call back that takes a while, you could spawn
a thread and turn the work over to a thread, and then return control of
the GUI thread back to the main loop.

> 
> 3. pg 130 of Summerfield
> 
> class ZeroSpinBox(QObject):
                   ^^^^^^^^
I'm guessing that should be QSpinBox, not QObject, as this class is just
adding behavior to the spinbox.  If the book really has it as QObject, I
would suspect it is a mistake.  But without seeing the entire example
program, I cannot say.

>    def __init__(self, parent=None):
>       super(...blah initialize QObject with parent
>       self.connect(self, SIGNAL("valuedChanged(int)"), self.checkzero)
> 
>    def checkzero(self):
>       if self.value() == 0:
>          self.emit(SIGNAL("atzero"), self.zeros)
> 
> basically when SpinBox gets set to zero we emit 'atzero' and return a zeros 
> counter.
> 
> What i don't get is the valueChanged(int) bit.. he clearly defines an (int) 
> being passed to checkzero so shouldn't the definition reflect the same? 
> Mistake?

Normally it should match the signature, yes.  Whether this is a mistake,
I don't know.  I believe PyQt could be ignoring the parameters the slot
callback doesn't have defined. And since he's calling self.value() to
read the actual value, he doesn't really need the int parameter, though
if it were me I'd use the parameter and not call self.value() at all.

> A. additionally I did not understand short circuit signals where you can
> drop the (type1, type2) in the signature and just do
> SIGNAL("atzero")
> 
> Is this doable ONLY in emit() or also in connect()??

As far as I know, only in the emit().  I'm not familiar with this "short
circuit" thing.  In fact I've never heard of it and have done a few
things in PyQt.

For defining signals, PyQt has defined a new method for creating signals
that's much cleaner than using the SIGNAL() call:

class ZeroSpinBox(QSpinbox):
    atzero = PyQt4.pyqtSignal() #could list arguments here if desired
    someother = PyQt4.pyqtSignal('int','bool')

    ...

    def checkzero(self, value):
	if value == 0:
	    self.atzero.emit()

Note that parameters to pyqtSignal can be either Python types that PyQt
can marshall (int, float,str, any QObject-derived class etc), or C++
types as a name in a string.

> 4. pg 131 'any data can be passed as arguments to the emit method and they 
> can be passed as Python objects'
> 
> self.emit(SIGNAL("atzero"), 10, 20, 30) or 
> self.emit(SIGNAL("atzero"), [10, 20, 30])
> does he mean i can pass a SINGLE OBJECT of any python type or does he mean 
> multiple independent containers?
> (assuming the definition matches)

You can pass any number of extra parameters when emitting a signal.
I've not used this feature before, so I'm not sure how it works exactly.

> 
> 5. He says 'a signal with one argument is a Qt signal or a non-short-circuit 
> Python signal' 
> 
> So if I have a Qt widget in C++ and it emits a valueChanged(int) signal.. 
> okay he's going to have to convert our asm/x86 int to python-integer and 
> then call the python-method that was mapped to 'valueChanged' with the 
> python-integer argument OR the signal is being generated from Python code 
> itself AND WILL be converted to C++ datatype - huh???? why??

Basically what it means is that all the types used in signals are native
C++ types. That's because, well, Qt is a C++ library. So at some point
python objects have to be marshalled into C++ data types, and the
reverse is also true.

> I'm guessing there's some dispatch code that maintains a mapping table?? SO 
> why can't it differentiate between inhouse python widget and Qt-c++ widget??

Yes.  It can't because it's written in C++. Any time you want to go
between the C++ backend and Python, you have to marshall between

> 6. "If we use the SIGNAL() function with a signalSignature (a possibly empty 
> parenthesized list of comma-separated PyQt types)..."??
> 
> how can it be an empty () list..?? If there's a list it's no longer empty..

He just means the call itself.  SIGNAL() defines a signal with no
signature.  SIGNAL('int') defines a signal with one parameter, which
will be a C++ int type (or Python integer)

> 
> 7. What exactly is a Python signal vs a Qt signal originating from a C++-
> asm-widget?? PyQt is a wrapper around C++Qt so.. where are the python 
> widgets??

That's a complicated question. Basically every PyQt object in Python
consists of two objects. One is the underlying C++ QObject-based object
which is opaque to Python, and one is the Python wrapper around that
object that exposes the Qt object's attributes and methods to Python.
Each call into Qt requires converting Python objects into C++ data
types, and information returned from Qt calls is converted (wrapped)
into python objects.  This process is not always pythonic and sometimes
you get C++-ism and Qt-isms such as QString's or QList's leaking into
Python code.

A signal emitted from Python code calls in the same C++ mechanism that
dispatches signals from C++Qt code.  It all goes through Qt, which is
C++.  In other words, signals are not propagated at the Python level,
but rather at the underlying C++ level.  The main loop is C++ and it's
in charge. when a callback is executed, if it's a PyQt callback, it will
be bubbled up through the Python interpreter into your python code, from
Qt in C++.


> 8. 
> class TaxRate(QObject):
>   def __init__(self):
>     super(TaxRate, self).__init__()
>     self.__rate = 17.5
> 
>   def rate(self):
>     return self.__rate
> 
>   def setRate(self, rate):
>     if rate != self.__rate:
>        self.__rate = rate
>        self.emit(SIGNAL("rateChanged"), self.__rate)
> 
> def rateChanged(value):
> 	print "TaxRate changed to %.2f%%" % value
> 
> vat = TaxRate()
> vat.connect(vat, SIGNAL("rateChanged"), rateChanged)
> vat.setRate(17.5) # No change will occur (new rate is the same)
> vat.setRate(8.5) # A change will occur (new rate is different)
> 
> Isn't this a mistake? self.connect( should be:
> vat.connect(vat, SIGNAL("rateChanged"), rate)

No.  rateChanged is correct.  There is no "rate" in the scope where
where the vat.connect() call is made.





More information about the Python-list mailing list