[PyQt] pyqtsignal() and None value

J Barchan jnbarchan at gmail.com
Fri Sep 7 08:03:40 BST 2018


On 6 September 2018 at 21:10, Maurizio Berti <maurizio.berti at gmail.com>
wrote:

> I don't know specifically how using QVariant could change the behavior:
> I'm always using sip api version 2 also with Python 2 and Qt4 for
> everything, as I find it's much more simpler and usable since it
> automatically converts python types without using .toPyObject(). Note
> that, by using sip v2 for everything, QtCore will not wrap QVariant nor
> QString/QStringList (the latter is the default for Py3). You can specify
> the C++ signature type using quotes, though, such as pyqtSignal(['QString',
> 'QVariant']), which can be useful where you don't have full control over
> the slots, like in designer plugins.
>
> That said, as you might know, "object" is a much broader specification for
> the object type, meaning that it includes any kind of new-style python
> subclass ("object", indeed), including any PyQt wrapper. It can be useful
> when you want to emit signals using complex parameters (such as object
> references), but, in your case, QVariant should be enough.
>
> Maurizio
>
> 2018-09-06 9:29 GMT+02:00 J Barchan <jnbarchan at gmail.com>:
>
>>
>>
>> On 5 September 2018 at 18:38, Maurizio Berti <maurizio.berti at gmail.com>
>> wrote:
>>
>>> First of all, if anyone here has access to the documentation pages of
>>> PyQt, please read this (expecially the third point).
>>>
>>>
>>> I do not know your exact user case, so it's hard to give you specific
>>> suggestions, but here's what I'd do.
>>>
>>> 1. do not give any signature to the signal at all:
>>>
>>>     notifyProgress = QtCore.pyqtSignal(object, object)
>>>
>>> This allows you to use None for both arguments, but I'm not familiar
>>> with typing in methods for python 3 (to be fair, I didn't even know about
>>> it until yesterday, as I mainly use Python2). I did a quick test and it
>>> seems to work fine, though.
>>>
>>> 2. use a single parameter that will be sent as a tuple, and unpack it
>>> within the method:
>>>
>>>     notifyProgress = QtCore.pyqtSignal(object)
>>>     def updateProgress(self, *args):
>>>         val, text = args
>>>
>>>     [...]
>>>
>>>         self.notifyProgress.emit((None, "Some text"))
>>>         self.notifyProgress.emit((i / 2, None))
>>>
>>> 3. use an overloaded signal and two separate slots. This is something I
>>> was struggling for a lot of time and (thanks to you! :-D) I finally found
>>> out how it works, as it is *not* explained exhaustively in the
>>> documentation. Also, I think that's what you were referring to.
>>> As we know, signals can be connected "in two ways" (I'm referring to
>>> new-style connections). Normally, you can go by simply connecting the slot
>>> and eventually decorating that slot with the signature you are interested
>>> in; decoration is usually not strictly required, as slot that are
>>> processing invalid data are somewhat "ignored": I always thought it as some
>>> kind of try/except mechanism, if at some point within the method something
>>> wrong happens, the error is ignored but no exception is thrown; of course,
>>> using a decorated slot avoids unnecessary computation for wrong signature
>>> arguments.
>>> Sometimes, though, it's better if not necessary to connect the slot to
>>> the specific signature: self.somesignal[signature].connect(slot).
>>> What the documentation forgets to explain is that the same _has_ to be
>>> done when emitting custom overloaded signals if you are not using the
>>> default signature (the first argument).
>>>
>>>     #in this case, "str" is the default signature
>>>     notifyProgress = QtCore.pyqtSignal([str], [int])
>>>
>>>     [...]
>>>         #the default signature can be omitted
>>>         self.notifyProgress.connect(self.updateProgressString)
>>>         self.notifyProgress[int].connect(self.updateProgressInt)
>>>     [...]
>>>
>>>     @QtCore.pyqtSlot(str)
>>>     def updateProgressString(self, text):
>>>         [...]
>>>
>>>     @QtCore.pyqtSlot(int)
>>>     def updateProgressInt(self, val):
>>>         [...]
>>>
>>>     [...]
>>>
>>>         #again, the default signature can be omitted
>>>         self.notifyProgress.emit("Some text")
>>>         #now, *THIS*!
>>>         self.notifyProgress[int].emit(5)
>>>
>>> I really think that this is something the documentation should *not*
>>> miss. I understand that from the C++ point of view it's pretty obvious that
>>> a signal is emitted according to its argument type signature, but this is
>>> not that obvious for a common Python programmer.
>>>
>>> Obviously, in the last case there's a small drawback: you can't pass
>>> None as argument, as it will be converted to the typed signature, giving
>>> you an empty string or that infamous random 32bit integer. I don't think it
>>> would be an issue in your case, but it is something to keep in mind anyway.
>>>
>>>
>>> I hope this helps... it helped me :-)
>>>
>>> Maurizio
>>>
>>> 2018-09-05 12:33 GMT+02:00 J Barchan <jnbarchan at gmail.com>:
>>>
>>>>
>>>>
>>>> On 5 September 2018 at 09:11, J Barchan <jnbarchan at gmail.com> wrote:
>>>>
>>>>>
>>>>>
>>>>> On 4 September 2018 at 18:08, Maurizio Berti <maurizio.berti at gmail.com
>>>>> > wrote:
>>>>>
>>>>>> You are defining a specific signature in the signal:
>>>>>>
>>>>>> QtCore.pyqtSignal(int, str)
>>>>>>
>>>>>> this means that, despite the types you set in the method (which Qt
>>>>>> doesn't know anything of also, as you didn't use the Slot decorator),
>>>>>> whenever you emit a None (which is a NoneType, not int, nor str) Qt will
>>>>>> try to "translate" it to the valid signature you assigned.
>>>>>>
>>>>>> I don't know exactly why the int is that a high number (always high
>>>>>> and always random), but this probably makes sense for some C++ type
>>>>>> signature, as it seems to me that the number is always 32bit long and, in
>>>>>> my case, always negative.
>>>>>>
>>>>>> Anyway, if you really need to send None, you can use the generic
>>>>>> "object" signature in the signal definition, or, in your case, just go with
>>>>>> this, assuming the progress will never use negative numbers.
>>>>>>
>>>>>> def updateProgress(self, val: int=-1, text: str=''):
>>>>>>     if val is >= 0:
>>>>>>         self.progressBar.pb.setValue(val)
>>>>>>     if text:
>>>>>>         self.progressBar.label.setText(text)
>>>>>>
>>>>>> and then emit the text signal using -1 for val.
>>>>>>
>>>>>> Maurizio
>>>>>>
>>>>>>
>>>>>> 2018-09-04 18:16 GMT+02:00 J Barchan <jnbarchan at gmail.com>:
>>>>>>
>>>>>>> PyQt5.7.  I am having trouble `emit()`ing a signal and receiving its
>>>>>>> arguments correctly.  I have read http://pyqt.sourceforge.net/Do
>>>>>>> cs/PyQt5/signals_slots.html carefully.
>>>>>>>
>>>>>>> *Declaration*:
>>>>>>>
>>>>>>>     # class variable for "notifyProgress" signal, for displaying a
>>>>>>> progressbar
>>>>>>>     notifyProgress = QtCore.pyqtSignal(int, str)
>>>>>>>
>>>>>>> *Initialisation*:
>>>>>>>
>>>>>>> self.notifyProgress.connect(self.updateProgress)
>>>>>>>
>>>>>>> *Slot*:
>>>>>>>
>>>>>>>     def updateProgress(self, val: int, text: str):
>>>>>>>         # slot for self.notifyProgress
>>>>>>>         # eprpayrequestfunctions.runEpr() calls this to indicate
>>>>>>> progress
>>>>>>>         # if it passes an integer it's the desired value for the
>>>>>>> progressbar
>>>>>>>         # if it passes a string it's the desired value for the label
>>>>>>>         if val is not None:
>>>>>>>             self.progressBar.pb.setValue(val)
>>>>>>>         if text is not None:
>>>>>>>             self.progressBar.label.setText(text)
>>>>>>>
>>>>>>> *Signals*:
>>>>>>>
>>>>>>> 1. notifyProgress.emit(None, "Some text")
>>>>>>>
>>>>>>> 2. notifyProgress.emit(i / 2, None)
>>>>>>>
>>>>>>> *Behaviour in slot*:
>>>>>>>
>>>>>>> The problem is the passing of None from emit():
>>>>>>>
>>>>>>> 1. val arrives in slot as 1261196128.
>>>>>>>
>>>>>>> 2. text arrives in slot as '' (empty string).
>>>>>>>
>>>>>>> *Questions*:
>>>>>>>
>>>>>>>    - Where is this behaviour for None as an emit() parameter
>>>>>>>    documented?
>>>>>>>    - What is the correct/best way for handling this
>>>>>>>    correctly/easily?
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> Kindest,
>>>>>>> Jonathan
>>>>>>>
>>>>>>> _______________________________________________
>>>>>>> PyQt mailing list    PyQt at riverbankcomputing.com
>>>>>>> https://www.riverbankcomputing.com/mailman/listinfo/pyqt
>>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> È difficile avere una convinzione precisa quando si parla delle
>>>>>> ragioni del cuore. - "Sostiene Pereira", Antonio Tabucchi
>>>>>> http://www.jidesk.net
>>>>>>
>>>>>
>>>>> I'm OK with the val: int=-1, but not with the text: str=''.  An int
>>>>> of -1 is happily an invalid value for me, butt the trouble is a str
>>>>> of '' is perfectly legal, and will be used :(
>>>>>
>>>>> (which Qt doesn't know anything of also, as you didn't use the Slot
>>>>>> decorator),
>>>>>>
>>>>>
>>>>> I don't mind adding a slot decorator if that would help.  But
>>>>> presumably it would not here, as you're saying my None value does not
>>>>> match the type correctly anyway, right?
>>>>>
>>>>> I did consider making it so there is only one parameter, and the type (
>>>>> int or str) indicates which of the two paths to follow in the slot.
>>>>> I followed  http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html
>>>>> carefully where it describes"The following code demonstrates the connection
>>>>> of overloaded signals:" but I couldn't get it to work: it *always*
>>>>> called the first/default overload, regardless of the parameter type ... :(
>>>>>
>>>>> I'm now thinking: could I cut my losses and pass (and receive?) a
>>>>> single parameter as an explicit QVariant (which we don't usually use
>>>>> from PyQt), so that a single receiver slot can correctly see the original
>>>>> type --- would that work/be advisable?
>>>>>
>>>>>
>>>>> --
>>>>> Kindest,
>>>>> Jonathan
>>>>>
>>>>
>>>> OK, I gave QVariant (single parameter) a try.  *All* I had to was:
>>>>
>>>> 1. Declare signal as:
>>>>
>>>> QtCore.pyqtSignal(QVariant)
>>>>
>>>> 2. Write slot like:
>>>>
>>>> def updateProgress(self, val):
>>>>     if type(val) is int or type(val) is float:
>>>>         self.progressBar.pb.setValue(val)
>>>>     elif type(val) is str:
>>>>         self.progressBar.label.setText(val)
>>>>
>>>> 3. And then it works correctly with both emitter types (without caller
>>>> bothering with QVariant), like:
>>>>
>>>> notifyProgress.emit(50)
>>>> notifyProgress.emit("Finishing...")
>>>>
>>>> Is there any reason I should *not* be doing this, because it seems
>>>> perfect to me?
>>>>
>>>>
>>>>
>>>> --
>>>> Kindest,
>>>> Jonathan
>>>>
>>>
>>>
>>>
>>> --
>>> È difficile avere una convinzione precisa quando si parla delle ragioni
>>> del cuore. - "Sostiene Pereira", Antonio Tabucchi
>>> http://www.jidesk.net
>>>
>>
>> Hi Maurizio,
>>
>> First, thank you for your time on this matter.
>>
>> I think the bit you identified which I would not have tried is the
>> (non-documented) need to invoke the non-default overload via
>>         #now, *THIS*!
>>         self.notifyProgress[int].emit(5)
>> But I would still welcome your comment on my last post?  There I said I
>> have solved the whole thing very simply, just by using
>>
>> QtCore.pyqtSignal(QVariant)
>>
>> as my signal declaration.  To be clear: although I originally asked for
>> two separate parameters or overloads for the string versus the int, in my
>> case I am quite content with one at a time (calling code tends *either*
>> to pass the int *or* the string, it doesn't need both in one call).
>>
>> My problem then was the type for the parameter, to cater for either str
>> or int.  I chose QVariant, and it works fine for me.  Your suggestion
>> seems to be object (I don't know what that maps to in C++).  Do you
>> think it makes any difference if I stick with my choice?
>>
>> Thanks.
>>
>> --
>> Kindest,
>> Jonathan
>>
>
>
>
> --
> È difficile avere una convinzione precisa quando si parla delle ragioni
> del cuore. - "Sostiene Pereira", Antonio Tabucchi
> http://www.jidesk.net
>

Hi Maurizio,

Thanks for your information.  I am indeed Python 3/PyQt 5, I haven't known
any different.  I'm not such an expert as you on understanding the ins &
outs of Python/C++ wrappers etc.  I just learn to use what works and cross
my fingers :)

For now QVariant parameter type seems to be working fine for me, so I'll
just stick with it.  But will bear object in mind possibly for the future.
Mind you, I thought the documentation implied that the parameter type had
to be mappable to a C++ type: that's OK for QVariant, but if it's the case
then I can't see what type object would map to in C++?

Anyway, thanks for all your time & interest.

Ciao!


-- 
Kindest,
Jonathan
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20180907/84169b16/attachment-0001.html>


More information about the PyQt mailing list