Create lambda functions in a loop for PyQt5 Signals? [duplicate] - python

I have got a loop. I created a QCheckBox and put it in a QTableWidget cell, and everything is Ok. In each step of loop I have called a connect function, for myslot SLOT, but only the last QCheckBox instance is applied. I googled a lot and have found many people have my problem. I have applied their solutions, but my problem remains.
for row in xrange(len(uniqueFields)):
instance = QtGui.QCheckBox(uniqueFields[row], findInstance.tableWidget)
print QtCore.QObject.connect(instance,
QtCore.SIGNAL(_fromUtf8("stateChanged (int)")),
lambda: findInstance.projectsInstance.myslot(
"TWCH", findInstance, instance.text(),
instance.checkState(), instance))
findInstance.tableWidget.setRowCount(findInstance.tableWidget.rowCount() + 1)
findInstance.tableWidget.setCellWidget(row, 0, instance)
Note: my connect function return True.
How to create connect function in a loop that enumerates all of the instances?

Put the loop variable in a default argument, like this:
lambda state, instance=instance: findInstance.projectsInstance.myslot(
"TWCH", findInstance, instance.text(), instance.checkState(), instance)
This will give each lambda its own local copy of the instance variable.
EDIT
Here's a simple script that demonstrates how to use default lambda arguments:
from PyQt4 import QtGui
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QVBoxLayout(self)
for index in range(4):
instance = QtGui.QCheckBox('Checkbox(%d)' % index, self)
instance.stateChanged.connect(
lambda state, instance=instance:
self.mySlot(instance.text()))
layout.addWidget(instance)
def mySlot(self, text):
print('clicked: %s' % text)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

I have same problem , you should use functools.partial such as:
for key, val in a_DICT_THAT_YOU_STORED_YOUR_OBJECTS_AND_STRINGS:
obj = partial( findInstance.projectsInstance.myslot,arg1="TWCH",arg2=self,arg3=key,arg4=val.checkState() )
QtCore.QObject.connect(val, QtCore.SIGNAL(_fromUtf8("stateChanged (int)")), obj)
Of course, argX should set to your real name of your argument of your function name.

The problem is that you are creating a function using lambda where some of the variables inside the function are not being passed in as arguments to the function. When the lambda function is executed, when the signal is emitted, it uses the value of those variables (like instance) at that moment in time. To be clear, every lambda function you make is using the value of instance at runtime, rather than define time. So instance only holds a reference to the object used in the last iteration of our loop, which explains the behaviour you are seeing.
Some useful information can be found here (read the comments too) http://eli.thegreenplace.net/2011/04/25/passing-extra-arguments-to-pyqt-slot/
From the comments of the above link:
What you can do is have another function generate the lambda, i.e.
something like:
def make_callback(param):
return lambda: self.on_button(param)
And in the connection, call make_callback(i). Then a different lambda is
created for each iteration.
So you would want to generalise this and pass in things like instance to the make_callback function and then place your lambda definition inside the make_callback function. I would provide a clear example of this, but as the other answer says, your formatting appears to have become very messed up in your question and I would likely get it wrong for your specific application. If you aren't following what I've said, make the code in your question clearer and I'll have a go at creating an example!

Related

Understanding Python Lambda behavior with Tkinter Button [duplicate]

This question already has answers here:
Why is my Button's command executed immediately when I create the Button, and not when I click it? [duplicate]
(5 answers)
Closed 1 year ago.
I would like to understand how a button is working using lambda.
I have the following Python code:
from tkinter import *
def comando_click(mensagem):
print(mensagem)
menu_inicial = Tk()
menu_inicial.geometry("500x250+200+200")
botao = Button(menu_inicial, text = "Executar", command=comando_click("Nova_Mensagem"))
botao.pack()
menu_inicial.mainloop()
But my button doesn't work when I click on it, it only shows the print once in the console when I run the code, I added some prints here in the question:
Problem Picture one
Well it seems that when I use the Lambda function in the button it works and I really would like to know why.
Lambda working button Picture one
I just added to the button the lambda :
botao = Button(menu_inicial, text = "Executar", command=lambda:comando_click("Nova_Mensagem"))
Lambda working button Picture two
Why with lambda it works?
It shoudn't work without lambda too since lambda is basically a anonymous function?
I am extremely curious to understand why it works, thank you all for the help :)
Edit: I would like to thank you guys, now I finally understand what was going on and how Python was working. Thank you all very much :D
When you use () with a function name(func(args)), then it is immediately calling/invoking the function while python is executing the line, you do not want that. You want to ONLY call the function when the button is clicked. tkinter will internally call the function for you, all you have to do is give the function name.
Why use lambda? Think of it as a function that returns another function, your code can be lengthened to:
func = lambda: comando_click("Nova_Mensagem")
botao = Button(menu_inicial, text = "Executar", command=func)
func is the function name and if you want to call it, you would say func(). And when you say command=comando_click("Nova_Mensagem") then command has the value returned by command click(because you call the function with ()), which is None and if I'm not wrong, if the given value is None, it will not be called by tkinter. Hence your function is executed just once because of () and as a result of calling the function, you are assigning the value of the function call(None) before the event loop starts processing the events.
Some other methods:
Using partial from functools:
from functools import partial
botao = Button(.....,command=partial(comando_click,"Nova_Mensagem"))
Using a helper function:
def helper(args):
def comando_click():
print(args)
return comando_click
botao = Button(...., command=helper("Nova_Mensagem"))
IMO, lambdas are the easiest way to proceed with calling a function with arguments.
In this code:
command=comando_click("Nova_Mensagem")
you have called the comando_click function, once, and assigned the result (None) to the command argument. Nothing will happen when command is called (in fact you should get a TypeError exception because None is not callable).
In this code:
command=lambda:comando_click("Nova_Mensagem")
you have not actually called comando_click yet -- you have created a new function (using lambda) that will in turn call comando_click when it is called. Every time the button is clicked, your new function will get called.
If the lambda is confusing, you can do the exact same thing with a def like this:
def button_command():
comando_click("Nova_Mensagem")
...
command=button_command # no ()! we don't want to actually call it yet!
The lambda expression is just an alternative to using def when you want to create a small single-use function that doesn't need a name (e.g. you want to make a function that calls another function with a specific argument, exactly as you're doing here).
The issue is that with comando_click("Nova_Mensagem") you are executing the function. So command=None.
In the second case lambda:comando_click("Nova_Mensagem") is returning a lambda, that internally calls comando_click("Nova_Mensagem").
Fix: just put command=comando_click.
If you want to personalize the lambda with arguments you could write something like this:
def handler(args):
def custom_handler():
print(args)
return custom_handler
botao = Button(menu_inicial, text = "Executar", command=handler("my custom string"))

Function executes correctly as a tkinter button command but not in the script

This is my first question on StackOverflow. So far lurking was enough to solve all my problems.
I'm a python newbie and I don't fully understand the meaning behind 'self' yet.
I defined a function (not a method. It's not inside a class) as
def pcal_thresh(self):
p_th = p_thresh.get()
print('p_th')
I am trying to use it in 2 separate conditions. First as a command for Tkinter
p_thresh = tk.Scale(calibration, from_=255, to=1, length=int(y_height*1.2), command=pcal_thresh)
Second, inside another function
def confirm():
if not top_distance == 0:
pcal_thresh()
In this exact configuration the function "pcal_thresh()" executes correctly as a Tkinter command, but not inside another function. If I remove 'self' from the declaration, it's the opposite. Works fine when used inside a function, but not as a Tkinter command. What can be the issue here?
self does not have a default value, so even if you don't use it, you still need to provide a value when you call pcal_thresh. As a callback, it receives the new scale value when called.
Either provide a dummy argument
def confirm():
if not top_distance == 0:
pcal_thresh(None)
or provide a default value:
def pcal_thresh(self=None):
p_th = p_thresh.get()
print('p_th')

Can I utilize Pyside clicked.connect to connect a function which has a parameter

I want to have a function in main class which has parameters not only self.
class Ui_Form(object):
def clearTextEdit(self, x):
self.plainTextEdit.setPlainText(" ")
print("Script in Textbox is Cleaned!",)
x will be my additional parameter and I want clearTextEdit to be called by click.
self.pushButton_3.clicked.connect(self.clearTextEdit(x))
it does not allow me to write x as parameter in clicked. Can you help me!
Solution
This is a perfect place to use a lambda:
self.pushButton_3.clicked.connect(lambda: self.clearTextEdit(x))
Remember, connect expects a function of no arguments, so we have to wrap up the function call in another function.
Explanation
Your original statement
self.pushButton_3.clicked.connect(self.clearTextEdit(x)) # Incorrect
was actually calling self.clearTextEdit(x) when you made the call to connect, and then you got an error because clearTextEdit doesn't return a function of no arguments, which is what connect wanted.
Lambda?
Instead, by passing lambda: self.clearTextEdit(x), we give connect a function of no arguments, which when called, will call self.clearTextEdit(x). The code above is equivalent to
def callback():
return self.clearTextEdit(x)
self.pushButton_3.clicked.connect(callback)
But with a lambda, we don't have to name "callback", we just pass it in directly.
If you want to know more about lambda functions, you can check out this question for more detail.
On an unrelated note, I notice that you don't use x anywhere in clearTextEdit. Is it necessary for clearTextEdit to take an argument in the first place?

QtCore.QObject.connect in a loop only affects the last instance

I have got a loop. I created a QCheckBox and put it in a QTableWidget cell, and everything is Ok. In each step of loop I have called a connect function, for myslot SLOT, but only the last QCheckBox instance is applied. I googled a lot and have found many people have my problem. I have applied their solutions, but my problem remains.
for row in xrange(len(uniqueFields)):
instance = QtGui.QCheckBox(uniqueFields[row], findInstance.tableWidget)
print QtCore.QObject.connect(instance,
QtCore.SIGNAL(_fromUtf8("stateChanged (int)")),
lambda: findInstance.projectsInstance.myslot(
"TWCH", findInstance, instance.text(),
instance.checkState(), instance))
findInstance.tableWidget.setRowCount(findInstance.tableWidget.rowCount() + 1)
findInstance.tableWidget.setCellWidget(row, 0, instance)
Note: my connect function return True.
How to create connect function in a loop that enumerates all of the instances?
Put the loop variable in a default argument, like this:
lambda state, instance=instance: findInstance.projectsInstance.myslot(
"TWCH", findInstance, instance.text(), instance.checkState(), instance)
This will give each lambda its own local copy of the instance variable.
EDIT
Here's a simple script that demonstrates how to use default lambda arguments:
from PyQt4 import QtGui
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QVBoxLayout(self)
for index in range(4):
instance = QtGui.QCheckBox('Checkbox(%d)' % index, self)
instance.stateChanged.connect(
lambda state, instance=instance:
self.mySlot(instance.text()))
layout.addWidget(instance)
def mySlot(self, text):
print('clicked: %s' % text)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I have same problem , you should use functools.partial such as:
for key, val in a_DICT_THAT_YOU_STORED_YOUR_OBJECTS_AND_STRINGS:
obj = partial( findInstance.projectsInstance.myslot,arg1="TWCH",arg2=self,arg3=key,arg4=val.checkState() )
QtCore.QObject.connect(val, QtCore.SIGNAL(_fromUtf8("stateChanged (int)")), obj)
Of course, argX should set to your real name of your argument of your function name.
The problem is that you are creating a function using lambda where some of the variables inside the function are not being passed in as arguments to the function. When the lambda function is executed, when the signal is emitted, it uses the value of those variables (like instance) at that moment in time. To be clear, every lambda function you make is using the value of instance at runtime, rather than define time. So instance only holds a reference to the object used in the last iteration of our loop, which explains the behaviour you are seeing.
Some useful information can be found here (read the comments too) http://eli.thegreenplace.net/2011/04/25/passing-extra-arguments-to-pyqt-slot/
From the comments of the above link:
What you can do is have another function generate the lambda, i.e.
something like:
def make_callback(param):
return lambda: self.on_button(param)
And in the connection, call make_callback(i). Then a different lambda is
created for each iteration.
So you would want to generalise this and pass in things like instance to the make_callback function and then place your lambda definition inside the make_callback function. I would provide a clear example of this, but as the other answer says, your formatting appears to have become very messed up in your question and I would likely get it wrong for your specific application. If you aren't following what I've said, make the code in your question clearer and I'll have a go at creating an example!

Python 2.7 : Tkinter, How to use the bind method?

I'm trying to create a Scrabble game with Python. I'd like to display the points that the word worth when the user is typing the word.
I already asked this question as I didn't know what method to use. As I discovered which method to use, and my question is about how to use this method, I think this deserve a new question.
My problem is that I created a function called bind_entry(event) that is supposed to set a label every time the user type a letter. But the function bind_entry(event)doesn't know the label to set and the entry where the word is.
Here is my code :
#this the function creating the label
def create_variabletext_intlabel(root,col,row):
val=IntVar()
label=Label(root,textvariable=val)
label.grid(column=col,row=row)
return val, label
#this is the function creating the entry
def create_entry_string(root,width,col,row,columnspan,rowspan):
val=StringVar()
entry=ttk.Entry(root,width=width,textvariable=val)
entry.grid(column=col,row=row,columnspan=columnspan,rowspan=rowspan)
entry.bind("<Any-KeyPress>",bind_entry)
#Here is my problem, when I call the function bind_entry.
return val, entry
def bind_entry(event):
label.set(m.counting_point(char(event)))
# m.counting_point() is a function counting the word's points
# my problem is that the function doesn't know yet the label.
# I don't know how to call the label.
# I call the function create_entry_string in another file initiating
# all the widget for the GUI
val_entry_word, entry_word =g.create_entry_string(root,15,1,1,1,1)
# I call the function create_variabletext_intlabel in another file
# initiating all the widget for the GUI
val_points,label_points=g.create_variabletext_intlabel(root,1,2)
I just noticed that the function m.counting_points() will count only the letter that is typed by the user. Here I should call val_entry_word.
So here is my question :
As val_entry_word and val_points are created in a function in another file How could I call val_entry_word and val_points in the function bind_entry() ?
Generally, when you need different function calls to share information without passing it explicitly, the best practice is to use a class.
e.g.
class LabelUpdater(object):
def create_variabletext_intlabel(self,root,col,row):
val=IntVar()
self.label=label=Label(root,textvariable=val)
label.grid(column=col,row=row)
return val, label
#this is the function creating the entry
def create_entry_string(self,root,width,col,row,columnspan,rowspan):
val=StringVar()
entry=ttk.Entry(root,width=width,textvariable=val)
entry.grid(column=col,row=row,columnspan=columnspan,rowspan=rowspan)
entry.bind("<Any-KeyPress>",self.bind_entry)
#Here is my problem, when I call the function bind_entry.
return val, entry
def bind_entry(self,event):
self.label.set(m.counting_point(char(event)))
#At this point, I don't understand your code anymore since I don't know what g
#is or how it's create_entry_string method calls your create_entry_string function...
#I'll assume that the module where g's class is defined imports this file...
#If that's how it works, then the following may be ok, although probably not because
#of circular imports...
container=LabelUpdater()
create_variabletext_intlabel=container.create_variabletext_intlabel
create_entry_string=container.create_entry_string
val_entry_word, entry_word =g.create_entry_string(root,15,1,1,1,1) #somehow calls create_variabletext_intlabel which is mapped to container.create_variable_intlabel???
# I call the function create_variabletext_intlabel in another file
# initiating all the widget for the GUI
val_points,label_points=g.create_variabletext_intlabel(root,1,2)
Of course, you could also use globals...(though that is definitely discouraged)
Finally, an idiom that I often use to add additional information in a bind callback is to wrap the callback function in another function...
def myfunc(root):
label=Label(root,text="cow")
label.pack()
return label
#This is the callback we want...
# Q: but how do we pass S?
# A: we need to wrap this call in another -- a perfect use for lambda functions!
def change_label(label,S):
label.config(text=S)
root=Tk()
lbl=myfunc(root)
lbl.bind("<Enter>",lambda e: change_label("Horse"))
lbl.bind("<Leave>",lambda e: change_label("Cow"))

Categories

Resources