Reply
Thread Tools
Posts: 79 | Thanked: 20 times | Joined on Apr 2010
#1
There must be something I am missing about why Qt's signals are so cool, at least from the Python point of view.

I specifically have problems with a case where I want to provide some arguments to a function called as a result of a signal.

Take for example the case of clicking a button and this calling a function with some specific argument, say a string to be printed.

In PyGtk I can say:
button.connect("clicked", print_function, "String to print")

And define the function with nothing extra special to it:
def print_function(widget, print_string) : print print_string

In PyQt I need to define a lambda function to accomplish the same, in PySide I need to use some @ directives to create a specific signal with parameters or something. On top of other aggravation PyQt and PySide need to have different syntaxes/constraints for doing this.

A more realistic example is creating a QStackedArea and some buttons to change what is shown in the area - having one function do the change, with the different buttons passing different content to show, seems inordinately complicated to accomplish, at least when comparing to the simplicity of PyGtk.

Since I am new to this Qt Python stuff there must be something I am missing. What is the correct/easy/elegant way of passing some not API-defined extra parameters to the target of a signal?
 
rm42's Avatar
Posts: 963 | Thanked: 626 times | Joined on Sep 2009 @ Connecticut, USA
#2
I believe that for the case you are refering to the canonical way to do it is using Python's 'functools.partial'.

import functools
buttonOneFunc = functools.partial(action, "One")
buttonTwoFunc = functools.partial(action, "Two")

You can then use the appropriate func to connect the appropriate object to the slot.
__________________
-- Worse than not knowing is not wanting to know! --

http://temporaryland.wordpress.com/
 
Posts: 15 | Thanked: 87 times | Joined on Dec 2009
#3
Originally Posted by mikaelh View Post
In PyQt I need to define a lambda function to accomplish the same, in PySide I need to use some @ directives to create a specific signal with parameters or something. On top of other aggravation PyQt and PySide need to have different syntaxes/constraints for doing this.
You should be able to use the so-called new-style signals and slots both on PySide and on PyQt, with the small difference that on PySide the signal and slot classes are called Signal and Slot and on PyQt pyqtSignal and pyqtSlot. If you want to use the same names on either backend, a simple assignment should work:

Code:
QtCore.pyqtSignal = QtCore.Signal
QtCore.pyqtSlot = QtCore.Slot
Also, the @ decorators should only be needed for slots requiring C++ signatures. If you're doing Python-Python signal and slot connections only, you should be able to do without the decorator. The following example also works if the decorator is commented out:

Code:
#!/usr/bin/python

import sys
from PySide import QtCore

# define a new slot that receives a string and has
# 'saySomeWords' as its name
@QtCore.Slot(str)
def saySomeWords(words):
    print words

class Communicate(QtCore.QObject):
    # create a new signal on the fly and name it 'speak'
    speak = QtCore.Signal(str)

someone = Communicate()
# connect signal and slot
someone.speak.connect(saySomeWords)
# emit 'speak' signal
someone.speak.emit("Hello everybody!")
The following page gives a few examples (unfortunately the examples still use QStrings which need to be replaced by native strs - I've submitted a merge request to fix the page but it hasn't been processed yet):

http://www.pyside.org/docs/pyside/newsigslot.html
 

The Following 7 Users Say Thank You to mairas For This Useful Post:
Posts: 79 | Thanked: 20 times | Joined on Apr 2010
#4
Thanks guys. I seemed to encounter another problem with PySide that I did not have with PyQt: it is not allowed to link a signal to a Python method that is part of a class that inherits one of the Qt classes, apparently because the inherited class is still C++, if you ask PySide, and you are not allowed to define dynamic slots in PySide Thus, following rm42's lead I defined the following convenience function:

Code:
def link(source, signal, target, *args, **kwargs) :
  proxy_target = functools.partial(target, *args, **kwargs)
  source.connect(source, SIGNAL(signal), proxy_target)
This works admirably for me, and looks pythonic to boot. For example, I can simply say:

Code:
link(web_page, "loadFinished(bool)", web_page.load_finished)
... and it works even in PySide even if load_finished() is a function in a class inherited from QWebPage. And I do not need to do anything special to the method to mark it as a slot.

I also defined two additional convenience functions:

Code:
def linkSource(source, signal, target, *args, **kwargs) :
  link(source, signal, target, *((source,) + args), **kwargs)

def linkClick(source, target, *args, **kwargs) :
  link(source, "clicked()", target, *args, **kwargs)
... covering the two common cases where I want to pass the calling widget to the target function, and a button pressed, respectively.

Now my original problem of changing QStackedLayout contents based on button clicks is covered by lines like:

Code:
linkClick(emailbutton, stack.setCurrentWidget, emailarea)
linkClick(calendarbutton, stack.setCurrentWidget, calendararea)
linkClick(contactbutton, stack.setCurrentWidget, contactarea)
And custom signals still work, in the vein of:

Code:
link(self, "kick_next_load()", self.load_next)
where the load_next() method gets appropriately invoked with a simple emit:

Code:
self.emit(SIGNAL("kick_next_load()"))
Now. all this works for me, but is it somehow wrong or against how Qt and PySide are intended to work? Especially, I do not see a lot of use for the new-style signals and slots, so I know there must be something I am not getting here.
 
Posts: 15 | Thanked: 87 times | Joined on Dec 2009
#5
Originally Posted by mikaelh View Post
Thanks guys. I seemed to encounter another problem with PySide that I did not have with PyQt: it is not allowed to link a signal to a Python method that is part of a class that inherits one of the Qt classes, apparently because the inherited class is still C++, if you ask PySide, and you are not allowed to define dynamic slots in PySide Thus, following rm42's lead I defined the following convenience function:
Could you do a huge favour and report this issue in the PySide Bugzilla (preferably together with example code, if possible). We'd absolutely like to nail down such issues before the 1.0 release!

Originally Posted by mikaelh View Post
Now. all this works for me, but is it somehow wrong or against how Qt and PySide are intended to work? Especially, I do not see a lot of use for the new-style signals and slots, so I know there must be something I am not getting here.
If the convenience functions work and make your life easier, then by all means, just use them.

I guess the issue with new-style signals and slots is that they're a rather recent addition both in PyQt and PySide, and therefore most of the code lying around is still written using the old syntax. Personally, I much prefer the new one since it's much cleaner, and not handling the signal and slot as strings should ensure typos are caught at a slightly earlier phase. I'm not sure about the implementation, but I suspect the new one might actually be slightly more efficient as well.

Last edited by mairas; 2010-08-23 at 07:45. Reason: Fixed a typo.
 
Posts: 79 | Thanked: 20 times | Joined on Apr 2010
#6
Bug 307 created.
 
Posts: 79 | Thanked: 20 times | Joined on Apr 2010
#7
Ok, as the bug now states, the problem only seems to affect classes inherited from QApplication.

Couple of further notes while we are on the subject:

1) Would appreciate it if the pyside documentation covered the basic use cases first: how to connect a signal to some function (this is covered) and how to connect a signal with a parameter to a function - e.g. how to connect the loadFinished(bool) signal of QWebPage using the new syntax? Should I use the bracket notation of the last example? What about signals with multiple parameters? Are there any in Qt with multiple parameters, for that matter?

2) Would it be possible to extend the new syntax to allow including parameters after the function name, i.e. widget.signal.connect(function, arg1, arg2, ...)?

I am happy to create documentation and other enhancement requests in bugzilla if these are in any way worthwhile.
 
Posts: 15 | Thanked: 87 times | Joined on Dec 2009
#8
Originally Posted by mikaelh View Post
Ok, as the bug now states, the problem only seems to affect classes inherited from QApplication.
Thanks for the bug report!

Originally Posted by mikaelh View Post
Couple of further notes while we are on the subject:

1) Would appreciate it if the pyside documentation covered the basic use cases first: how to connect a signal to some function (this is covered) and how to connect a signal with a parameter to a function - e.g. how to connect the loadFinished(bool) signal of QWebPage using the new syntax? Should I use the bracket notation of the last example? What about signals with multiple parameters? Are there any in Qt with multiple parameters, for that matter?
I can add the parameter cases to the documentation.

In the case of a single parameter, no magic is really required. Here's the hellowebkit.py example file, in which I connected the loadFinished signal to a Python function:
Code:
import sys
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtWebKit import *

def finished(succeeded):
    print succeeded

app = QApplication(sys.argv)

web = QWebView()
web.loadFinished.connect(finished)
web.load(QUrl("http://www.google.com"))
web.show()

sys.exit(app.exec_())
In the case of parameters with multiple signals, nothing special is required - just make your function accept the additional parameters. And yes, I believe there are plenty of such signals in Qt.

Originally Posted by mikaelh View Post
2) Would it be possible to extend the new syntax to allow including parameters after the function name, i.e. widget.signal.connect(function, arg1, arg2, ...)?
The best place to propose syntax modifications for PySide would be the PySide mailing list. If the proposal is well-received, a PSEP can then be written for the feature, after which there is a great probability it'll be implemented in the next major version. (Yes - the PySide API is defined in the open!)

However, in this particular case, the change would just be breaking compatibility with PyQt and the same functionality can be trivially achieved with functools.partial rm42 suggested or with simple lambda:
Code:
import sys
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtWebKit import *

def loadFinished(s,succeeded):
    print s,succeeded

app = QApplication(sys.argv)

web = QWebView()
web.loadFinished.connect(lambda b: loadFinished("I'm custom!",b))
web.load(QUrl("http://www.google.com"))
web.show()

sys.exit(app.exec_())
Originally Posted by mikaelh View Post
I am happy to create documentation and other enhancement requests in bugzilla if these are in any way worthwhile.
Yes, please! Bugzilla is always the best way to ensure your ideas, requests, or issues won't be forgotten and will be dealt with.
 
Posts: 79 | Thanked: 20 times | Joined on Apr 2010
#9
"Fail to add dynamic slot to QObject. PySide support at most 50 dynamic slots." -> Bug 312
 
Reply


 
Forum Jump


All times are GMT. The time now is 17:39.