This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 6 years ago.
It's in tkinter. The last line adds x = len(FRAME_LIST) "Buttons" into the dropdown menu. The problem is they all reference to the same frame (the last in the FRAME_LIST). How to I make it that every Button references a diffrent frame from FRAME_LIST?
for F in FRAME_LIST:
frame = ChallengePage(mainframe,self, F)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
subMenu1.add_command(label = F.id, command = lambda: self.show_frames(F))
EDIT:
So to be more precise, when I come across this problem i thought ok, the problem is I need to declare a local variable, so I tried this:
A = F
subMenu1.add_command(label = F.id, command = lambda: self.show_frames(A))
But it didnt work even though the A is declared INSIDE the loop, and redeclared in every loop, it still yields the same result.
I came now across the link: https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result
where it shows the solution:
subMenu1.add_command(label = F.id, command = lambda A = F: self.show_frames(A))
Which somehow magically works, but I don't get why it is any diffrent from my local A.
This is a common issue when using lambda in loops. I usually use a partial instead:
from functools import partial
for F in FRAME_LIST:
frame = ChallengePage(mainframe, self, F)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
subMenu1.add_command(label=F.id, command=partial(self.show_frames, F))
Because: closures.
To summarize: the lambda in your loop will remember the name of the variable (F), but not the value it points to on each iteration of the loop. By the time you are clicking the buttons and triggering the command, the name F points to the last value in the iteration of your for loop, and so that is what will be passed to your lambda. Partials on the other hand are aware of the object that F points to at each iteration, and keeps track of it so that self.show_frames() is called with the appropriate argument.
Related
This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 6 months ago.
I need to create multiple buttons with different names (each new name is equal to the name of the previous button + the iterating value at that moment.) Please help me out, here is my code.
buttons = [0]*len(gg.allStudents)
for j in range(len(gg.allStudents)):
buttons[j] = tk.Button(wind, text=gg.allStudents[j].name, height = 2, width = 20, command=lambda: plotMarks(j))
buttons[j].pack()
The looping conditions that I have used our correct. The only help I need is to find a way to store each new button with a new name into the 'buttons' list.
Your issue is not what you think it is. It can be easily solved by changing:
command=lambda: plotMarks(j)
to
command=lambda j=j: plotMarks(j).
The reason this works is because, in your version, you are sticking the variable j in all of your commands, and they will all use whatever the final value of j is. In the second version you are sticking the current value of j in your command.
To understand this better all we have to do is expand the lambdas.
def add2(n):
return n+2
#equivalent of your current version
j = 6
def currentLambdaEquivalent():
global j
print(add2(j))
currentLambdaEquivalent() #8
#equivalent of second version
def alternateLambdaEquivalent(j):
print(add2(j))
alternateLambdaEquivalent(2) #4
This question already has answers here:
Creating functions (or lambdas) in a loop (or comprehension)
(6 answers)
How can I identify buttons, created in a loop?
(6 answers)
Closed 4 years ago.
I'm having a problem where I create a for loop of buttons and I want each one of other to have different column, row and text.
I noticed only the text and the column (because i used lambda on it) are actually the updated values given to the function.
(btw the row and column are for something else, not the button - in a different function).
This is my code, I can't seem to understand how to get the most updated value for a button since all of my buttons are getting the values of the first button except for the text name and their column
for order in self.orders:
if counter2 >= 2 and counter2 % 2 == 0:
x += 1
print x, counter2
bt = Button(window, command=lambda i=counter2: self.display_order(self.orders[order], window, x, i))
bt.configure(text='order ' + str(counter2+1), fg='black', bg='steel blue', width=20)
bt.grid(sticky='W', row=0, column=counter2, columnspan=1) # increase row number for every button
counter2 += 1
x has also a problem with late binding. You've done it right for counter2. Use the following change (by adding x=x as additional lambda argument):
bt = Button(window, command=lambda i=counter2, x=x: self.display_order(self.orders[order], window, x, i))
My function isn't giving me the right output, and it doesn't want to work. I keep getting this error:
TypeError: list indices must be integers, not str
This is my code:
def showShop(level = level, cash = cash):
top = Tkinter.Tk()
shop = ["$100 & level 2 - Shotgun", "$250 & level 3 - 5 Grenades", "$500 & level 5 - Rocket Launcher"]
buttons = []
for i in shop:
temp = shop[i]
temp = Tkinter.Button(top, height=10, width=100, text = temp, command = shopping(i))
temp.pack()
buttons.append(temp)
top.mainloop()
I want it to display what is in the shop list based on what button it is...
Remove temp = shop[i] from the code
for i in shop:
temp = Tkinter.Button(top, height=10, width=100, text = temp, command = shopping(i))
temp.pack()
buttons.append(temp)
The for loop iterates over the elements in the list and not the indices!. The python docs make it more clear
The for statement in Python differs a bit from what you may be used to in C or Pascal. Rather than always iterating over an arithmetic progression of numbers (like in Pascal), or giving the user the ability to define both the iteration step and halting condition (as C), Python’s for statement iterates over the items of any sequence (a list or a string), in the order that they appear in the sequence.
Also note that the command argument in the Button constructor takes a function as an argument. So you maybe better off by writing command = shopping there instead of the call command = shopping(i).
Change for i in shop to for i in xrange(shop).
You have to use something like partial to pass arguments to the function called by the button press. Note that you have declared the variable "temp" as 2 different things. The only reason it works is because the second declaration is after you use the first. Also note that the "buttons" list can not be used outside of the function showShop() because it is created in/local to that function. The following is working code based on what you posted. Also, please do not use "i", "l" or "O" as single digit variable names as they can look like numbers.
import Tkinter
from functools import partial
def shopping(btn_num):
print "button number %d pressed" % (btn_num)
buttons[btn_num]["bg"]="lightblue"
def showShop(buttons):
top = Tkinter.Tk()
shop = ["$100 & level 2 - Shotgun", "$250 & level 3 - 5 Grenades",
"$500 & level 5 - Rocket Launcher"]
##buttons = []
for ctr in range(len(shop)):
temp = Tkinter.Button(top, height=10, width=100, text = shop[ctr],
command = partial(shopping, ctr))
temp.pack()
buttons.append(temp)
top.mainloop()
## lists are mutable
buttons=[] ## not local to the function
showShop(buttons)
This question already has answers here:
Local variables in nested functions
(4 answers)
Closed 9 years ago.
I am having some trouble with the button widgets.
Here's my code. Basically, what I would like to do is to print "0,0" when I press the first button, print "0,1" when I press the second button and so on. But what happens is that it always prints "1,1", which are last values in my for loops. How could I fix that?
Thanks a lot for helping
from tkinter import *
def show(x,y):
print(x,y)
root = Tk()
a = 0
for i in range(2):
for j in range(2):
Button(root, text=a, command=lambda:show(i,j)).grid(column=j, row=i)
a += 1
root.mainloop()
It is because of the closure property of Python. To fix this, change
Button(root, text=a, command=lambda:show(i,j)).grid(column=j, row=i)
to
def inner(i=i, j=j):
Button(root, text=a, command=lambda:show(i,j)).grid(column=j, row=i)
inner()
We are just wrapping the values of i and j with a function. Since the function show is not executed immediately, it just accepts the values of the two variables i and j. So, all the buttons now, will have the same i and j and they will get the values which are at the end of the loop.
Now, by defining a new function, we are getting a default parameter of the same names i and j and we get the values of i and j at the particular moment. So, the current values will be retained.
This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 4 years ago.
I have this code to create a series of bindings in a loop:
from Tkinter import *
keys = {0:'m', 1:'n', 2:'o'}
def SomeFunc(event=None,number=11):
print keys[number], number
root = Tk()
field = Canvas(root, height = 200, width = 200, bg = "gray")
for i in range(2):
root.bind("<KeyPress-%c>" % keys[i],lambda ev:SomeFunc(ev,i))
field.pack()
root.mainloop()
my problem is that when I press 'm' or 'n' the function SomeFunc gets called with the vairable 'i' as an argument. I would like it to be called with a 0 as argument (the numerical value 'i' had when 'bind' was used) when I press 'm' and with 1 when I press 'n'. Can this be done?
Your problem here is that the variable i gets captured by the lambda, but you can get around that by creating a little helper function for example:
for i in range(2):
def make_lambda(x):
return lambda ev:SomeFunc(ev,x)
root.bind("<KeyPress-%c>" % keys[i], make_lambda(i))
This creates a new scope for each binding you create, thus executing the for loop and changing of i during the loop does not influence your already lambda functions.