Using lambda expression to connect slots in pyqt
The QPushButton.clicked
signal emits an argument that indicates the state of the button. When you connect to your lambda slot, the optional argument you assign idx
to is being overwritten by the state of the button.
Instead, make your connection as
button.clicked.connect(lambda state, x=idx: self.button_pushed(x))
This way the button state is ignored and the correct value is passed to your method.
Using lambda expression to connect slots and send multiple values in pyqt
Thank you, @musicamante, for the explanation. I am just adding it in the answer to mark it as solved if someone has similar issues.
As they mentioned, the value of the keyword argument of lambda is evaluated when it's created. So, creating a reference to sensorName.currentText()
used the value of the current text at that time. But creating a reference to the ComboBox itself allowed for getting the value of the text in run-time.
Qt - Connect slot with argument using lambda
The issue is python's scoping rules & closures. You need to capture
the group:
def connections(self):
for group in self.widgets:
self.widgets[group].clicked.connect(lambda g=group: self.openMenu(g))
def openMenu(self,group):
print(group)
PyQt, Python 3: Lambda slot assigning signal argument to a variable?
I don't think you can (or should want to) do this with lambda
. See the answer to the possible duplicate (that I reference under your question) for what you can do.
One way to do something similar would be to simply define a single function which takes two arguments. The value, and some identifier you can use to look up the variable inside the function (eg an object reference or a string).
I might suggest:
object1.value_changed.connect(lambda val: store_var('var1', val))
object2.value_changed.connect(lambda val: store_var('var2', val))
How you implement store_var()
of course is highly dependent on the rest of yoru code structure, whether you want to store the values in a dictionary or as global variables or instance attributes. But you shouldn't have any problem accessing the storage location from within your function if you were expecting to be able to access them from within a lambda
expression.
Update
Based on your comment I assume the calls to the connect
function are inside a method of the class that contains data
. This you would define a method of that class as
def store_var(self, name, value):
setattr(self.data, name, value)
and then you would call
object1.value_changed.connect(lambda val: self.store_var('var1', val))
memory_profiler while using lambda expression to connect slots
The problem pointed out in this post does not have to do with PyQt5 but with the lambda functions since, like any function, it creates a scope and stores memory, to test what the OP points out I have created the following example:
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.counter = 0
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.on_timeout)
self.timer.start(1000)
self.button = QtWidgets.QPushButton("Press me")
self.setCentralWidget(self.button)
@QtCore.pyqtSlot()
def on_timeout(self):
self.counter += 1
if self.counter % 2 == 0:
self.connect()
else:
self.disconnect()
def connect(self):
self.button.setText("Connected")
self.button.clicked.connect(lambda checked: None)
# or
# self.button.clicked.connect(lambda checked, v=list(range(100000)): None)
def disconnect(self):
self.button.setText("Disconnected")
self.button.disconnect()
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
self.button.clicked.connect(lambda checked: None)
self.button.clicked.connect(lambda checked, v=list(range(100000)): None)
As observed at the time of connection and disconnection, the memory consumed increases since the lambda maintains the information of v=list(range(100000))
.
But in your code the closure stores only the variable "j" which is minimum:
getattr(self, i).clicked.connect(lambda pippo, j=i: self.printo(j))
To see how this variable affects I am going to eliminate the unnecessary code for the test (hangman005.py, etc) in addition to offering alternatives:
- without connect:
class MainMenu(QtWidgets.QMainWindow):
def __init__(self):
super(MainMenu, self).__init__()
uic.loadUi("main_window2.ui", self)
self.hangman()
def hangman(self):
letters = "ABCDEFGHIJKLMNOPQRSTYVWXXYZ"
for i in letters:
pass
def printo(self, i):
print("oooooooooooooooooooooo :", i)
- with lambda:
class MainMenu(QtWidgets.QMainWindow):
def __init__(self):
super(MainMenu, self).__init__()
uic.loadUi("main_window2.ui", self)
self.hangman()
def hangman(self):
letters = "ABCDEFGHIJKLMNOPQRSTYVWXXYZ"
for i in letters:
getattr(self, i).clicked.connect(lambda pippo, j=i: self.printo(j))
def printo(self, i):
print("oooooooooooooooooooooo :", i)
- with functools.partial
class MainMenu(QtWidgets.QMainWindow):
def __init__(self):
super(MainMenu, self).__init__()
uic.loadUi("main_window2.ui", self)
self.hangman()
def hangman(self):
letters = "ABCDEFGHIJKLMNOPQRSTYVWXXYZ"
for i in letters:
getattr(self, i).clicked.connect(partial(self.printo, i))
def printo(self, i):
print("oooooooooooooooooooooo :", i)
- once connect
class MainMenu(QtWidgets.QMainWindow):
def __init__(self):
super(MainMenu, self).__init__()
uic.loadUi("main_window2.ui", self)
self.hangman()
def hangman(self):
letters = "ABCDEFGHIJKLMNOPQRSTYVWXXYZ"
for i in letters:
getattr(self, i).setProperty("i", i)
getattr(self, i).clicked.connect(self.printo)
def printo(self):
i = self.sender().property("i")
print("oooooooooooooooooooooo :", i)
As we can see there is no significant difference between all the methods so in your case there is no memory leak or rather it is very small.
In conclusion: Every time you create a lambda method it has a closure (j = i
in your case) so the OP recommends taking that into account, for example in your case len(letters) * size_of(i)
are consumed which at Being small letters
and i
makes it negligible, but if you otherwise store a heavier object unnecessarily then it will cause problems
QT connect function using lambda
You can also use =
to capture by value or use '&' capture by reference.
connect(btn,
&QPushButton::clicked,
this,
[=]
{
F1(str1);
};
Lambda slots for QPushButtons in a QTableWidget
It's not working because the i passed as a parameter to btnSlot is evaluated at the time btnSlot is called, NOT at the time the lambda is define. You need to freeze the value of i using a partial function, so:
from functools import partial
btn.clicked.connect(partial(self.btnSlot,i))
Another approach is to take advantage of the fact that default parameters are evaluated at the time function is defined, NOT at call time. So:
btn.clicked.connect(lambda checked,num=i: self.btnSlot(num))
How to use lambda function with the PyQt connect function when the widgets are instanciated in a loop?
The variables used inside a lambda are always evaluated at execution: widgetNumber
will always be the last value in the for loop.
Also, signals can have arguments, and if you use a positional argument in the lambda, that argument will be the value of that signal when it's emitted: lambda widgetNumber:
will result in the spinbox value or the state of the check box.
If you need "static" arguments, you need to use keyword arguments. Since the signal has arguments, there's also no need to call the getter again:
lambda value, widgetNumber=widgetNumber: self.argumentChanged(widgetNumber, value)
Remember that lambdas should be preferably used only when there is no other, simpler (clearer or cleaner) solution.
In this case, we can use the sender()
function provided by Qt, which returns the object that emitted the signal.
In the following example I've simplified the whole loop, and used a custom property to set the number directly on the widget (note that also a basic attribute could be used, but properties are more consistent with Qt).
class MyWidget(QWidget):
def __init__(self, widgetList):
super(QWidget, self).__init__()
self.widgetList = widgetList
layout = QVBoxLayout()
for number, widgetType in enumerate(widgetList):
if widgetType == 'QCheckBox':
widget = QCheckBox()
widget.toggled.connect(self.argumentChanged)
elif widgetType == 'QSpinBox':
widget = QSpinBox()
widget.valueChanged.connect(self.argumentChanged)
widget.setProperty('widgetNumber', number)
layout.addWidget(widget)
self.setLayout(layout)
def argumentChanged(self, value):
widget = self.sender()
widgetNumber = widget.property('widgetNumber')
print("The widget number", widgetNumber,
"that is a", self.widgetList[widgetNumber],
"has been modified! New value:", value)
How to pass a QWidget's text through the parameter of Slot using lambda function
The next:
button_1.clicked.connect(lambda state, \
msg=self.mainWidget.layout().itemAt(0).widget().text(), \
srcIndex=0: self.showMessage(msg, srcIndex))
equals:
msg=self.mainWidget.layout().itemAt(0).widget().text()
srcIndex=0
button_1.clicked.connect(lambda state, msg=msg, srcIndex=srcIndex: self.showMessage(msg, srcIndex))
In reality you are passing the text when the window is being built and not the text that you have when the button is pressed.
The best thing is to create an intermediate function that gets the text:
button_1.clicked.connect(lambda state, lineedit=lineEdit_1: self.foo(lineedit))
button_2.clicked.connect(lambda state, lineedit=lineEdit_2: self.foo(lineedit))
def foo(self, lineedit):
self.showMessage(lineedit.text())
def showMessage(self, message):
msgBox = QMessageBox()
msgBox.setWindowTitle("Message")
msgBox.setIcon(QMessageBox.Information)
msgBox.setText("\"" + str(message) + "\"")
msgBox.setStandardButtons(QMessageBox.Yes)
rtnVal = msgBox.exec()
Lifetime of object in lambda connected to pyqtSignal
The lambda
in your example forms a closure. That is, it is a nested function that references objects available in its enclosing scope. Every function that creates a closure keeps a cell object for every item it needs to maintain a reference to.
In your example, the lambda
creates a closure with references to the local self
and model
variables inside the scope of the __init__
method. If you keep a reference to the lambda
somewhere, you can examine all the cell objects of its closure via its __closure__
attribute. In your example, it would display something like this:
>>> print(func.__closure__)
(<cell at 0x7f99c16c5138: MyModel object at 0x7f99bbbf0948>, <cell at 0x7f99c16c5168: MyClass object at 0x7f99bbb81390>)
If you deleted all other references to the MyModel
and MyClass
objects shown here, the ones kept by the cells would still remain. So when it comes to object cleanup, you should always explicitly disconnect all signals connected to functions that may form closures over the relevant objects.
Note that when it comes to signal/slot connections, PyQt treats wrapped C++ slots and Python instance methods differently. The reference counts of these types of callable are not increased when they are connected to signals, whereas lambdas, defined functions, partial objects and static methods are. This means that if all other references to the latter types of callable are deleted, any remaining signal connections will keep them alive. Disconnecting the signals will allow such connected callables to be garbage-collected, if necessary.
The one exception to the above is class methods. PyQt creates a special wrapper when creating connections to these, so if all other references to them are deleted, and the signal is emitted, an exception will be raised, like this:
TypeError: 'managedbuffer' object is not callable
The above should apply to PyQt5 and most versions of PyQt4 (4.3 and greater).
Related Topics
Python: Pandas Merge Multiple Dataframes
Random.Seed(): What Does It Do
How to Hide Console Window in Python
The Modulo Operation on Negative Numbers in Python
Putting a Simple If-Then-Else Statement on One Line
How to Highlight Text in a Tkinter Text Widget
Specifying and Saving a Figure with Exact Size in Pixels
How to Compare Version Numbers in Python
How to Round a Number to Significant Figures in Python
How to Pass Variables Across Functions
Using Os.Walk() to Recursively Traverse Directories in Python
How to Filter a Dictionary According to an Arbitrary Condition Function
Keep Only Date Part When Using Pandas.To_Datetime