lambdapyqtsignalsslot

PyQt QObject.connect(instancemethod) and QtCore.connect(Qobj, SIGNAL(), instancemethod) behave differently to lambda function


In follow up to Create PyQt menu from a list of strings i'm also creating menu's on the fly and want to know which menu item was clicked. Therefore I used a piece of code like:

for item in ADDABLE_OBJECTS:
     action = self.menuAdd.addAction(item)
     l = lambda item=item: self.doStuff(item)
     action.triggered.connect(l)

def doStuff(self, item):
  print "Item: ", item

Ouput:

Item: False (default 'bool' param as stated in Qt Docs for triggered() signal)

As I thought that object.connect() is the 'new' form as compared to connect(object, SIGNAL(...), slot). I also works better as the signal names can be checked at compile time. However, it did not work. All I got passed was the 'bool=false' (as stated in the Qt docs for triggered()). So after searching came across the above mention url. No I have this:

for item in ['One', 'Two', 'Three']:
    action = self.menuAdd.addAction(item)
    l = lambda item=item: self.doStuff(item)
    action.triggered.connect(l)
    self.connect(action, SIGNAL('triggered()'), l)

Which gives (on clicking the menu once):

Item: False (the action.triggered.connect(l) line, wrong) Item: One (the self.connect line, right)

Why do these two behave different? I have never noticed any difference between the two kinds of connect before, not until I used this lambda construction.


Solution

  • It is not about the lambda, but rather how the signals with default arguments are implemented in Qt.

    void triggered(bool = 0)
    

    is actually two signals:

    void triggered(bool)
    void triggered()
    

    PyQt docs mention the default overload for this signal as triggered(bool) and since your function accepts an argument it can successfully connect this signal to your function. Therefore the bool parameter is passed for the item.

    You can choose specific overloads by indexing syntax. In this case you want the signal with no parameters, so you would do:

    action.triggered[()].connect(l)