Loop changes button binding functions - python

sair_grid = self.root.ids["sairler_screen"].ids["sair_grid"]
for a in dler:
with open(a,"r",encoding="utf-8") as file1:
for b in file1:
btn = Button(text=b[:-5],on_press=lambda y:self.sairi(b))
sair_grid.add_widget(btn)
def sairi(self,x):
pass
No matter which button I clicked to calling the function, it is giving the same result. I want every button to take different b values and call the function with different arguments. All buttons created with different names but each of them calls with same last argument of the file. I am new in python and kivy so every help is really appreciated.

There is only one variable named b here and it changes its value until the last one remains last time the loop executes:
for b in file1:
btn = Button(text=b[:-5],on_press=lambda y:self.sairi(b))
sair_grid.add_widget(btn)
So, you are creating many lambdas, but they all use the same b from the closure.
A typical simple workaround for that problem is to bind the current value of b to a default value of the lambda argument:
for b in file1:
btn = Button(text=b[:-5],on_press=lambda y, b=b:self.sairi(b))
sair_grid.add_widget(btn)
Now each lambda you create has a locally bound b argument which has its own default value. The default value of b is now different for each lambda you create.

Related

Trying to understand in Python 3.7 how to get a number returned from a function to the main program

In general terms, I cannot get any function to return a value inside a variable (an integer, back to the main program. I have included the variable name as one of 2 arguments in the function (in this case mod_stone_value), and am using the return command to hopefully return that integer value to the main program, where that value is then added to a total value.
The key lines of code in the function would be:
def calc_real_stone_value(base_stone_value, mod_stone_value):
return mod_stone_value
and then, back in the main program:
total_stone_value = total_stone_value + mod_stone_value
The variable total_stone_value ends up being 0, yet non-zero values of mod_stone_value do print inside the function. I know I am doing something fundamentally wrong, but have no idea what it is.
If I understand correctly, you want to use the number value returned by the function calc_real_stone_value and are confused as to why it is not changing after you call your function which supposedly "updates it's value".
The fundamental thing you are misunderstanding is that primitive data types (double, int, char, etc.) are passed by value, not by reference. Therefore, if you make changes to the local variable mod_stone_value within the function, the changes will not show in the variable that exists outside the function. If you want to use the value you are returning with the line return mod_stone_value you need to call the function with the variables you want to be used in the calculations and then assign that value to a variable (mod_stone_value in this case).
Example:
base_stone_value = 1
mod_stone_value = 4
def calc_real_stone_value(base_stone_value, mod_stone_value):
# calculations
# e.g. add the two integer arguments
mod_stone_value += base_stone_value
return mod_stone_value
# print the values for mod_stone_value
print(mod_stone_value)
# call function and assign value to mod_stone_value
mod_stone_value = calc_real_stone_value(base_stone_value, mod_stone_value)
print(mod_stone_value)
Output:
4
5
Conclusion:
Primitive variables are passed by value and in order to retrieve the value from a function that returns an integer for example, it needs to be re-assigned.
To reduce confusion, you can avoid using variable names that shadow variable names from an outer scope.
Using your code as a template, here is an example that illustrates passing a value from "inside" the function to the outer level (here, the outermost level of the program). I would recommend reading this part of the official Python tutorial (or from the beginning if it does not make sense) as it describes concepts (e.g. global vs. local variables) that are relevant to your problem.
c = 0
d = 5
e = 2
def foo(a, b):
# insert whatever code here
d = 10
print(f"Inside foo(), d has value {d}")
# to match your example
return b
# c has the value 0, d has the value 5
print(f"(c, d) has the value ({c}, {d})")
# call function and pass return value to c
c = foo(d, e)
# c has the value 2, d has the value 5
print(f"(c, d) has the value ({c}, {d})")
By the way, regarding your code, statements inside the body of the function should be indented. This includes the return statement.

closure not correctly refering to correct variable

Here is a simple example showing a problem that I don't understand
callbacks = [lambda : 1, lambda : 2]
for i, c in enumerate(callbacks):
if i == 0:
cb1 = c
cb2 = lambda : c()
print(cb1()) # print 1
print(cb2()) # print 2
It seems that in cb1, I'm correctly able to copy the "correct" callback, ie the one I'm refering to in the loop iteration
However in cb2, even though I'm defining cb2 at a time when c refers to the 1st callback, it's updated afterwards to refer to the second callback
Can someone please shed some light on whatis going on ? this is pretty disconcerting
Is it possible that I write a lambda for cb2 and still refer to the first callback ?
This is expected behavior. To see why, consider the following example:
x = 1
def d():
return x
d() # returns 1
x = 2
d() # now returns 2
In the above example, the function d returns whatever value is assigned to the variable x at the time of execution. Variables in an enclosing scope are not copied in a function body, but referenced. The same is true if the variable holds a function value. Remember, functions are just values in Python. The behavior doesn't change just because you're using lambda syntax to define your functions.
The lambda is evaluated on runtime, when c already changed.
Try this instead:
cb2 = lambda c=c: c()
Using a default value, you can fixate a certain local variable.

Update Global Variables Input as Parameters Rather Than Returning Results From Function

Goal
I am trying to write a function where one or more of the input parameters is a global variable that is updated by the function, without having to return values from within the function. I am aware I could just return a tuple or two separate values from the function, but I think updating the global variables from within the function would be another interesting method if it is possible.
Reason to do this
Updating global variables with a function is easy when the global variable is known (ie. defined previously in the python script). However, I want to define the function in a separate .py file to easily use the function within other python scripts. Therefore, I need to be able to support different variable names to update.
While this is not at all necessary, I am just interested if this is even possible.
Example Pseudocode
I'm thinking something like this:
def math_function(input_val, squared_result, cubed_result):
squared_result = input_val**2 #update the var input as the squared_result parameter
cubed_result = input_val**3 #update the var input as the cubed_result parameter
where you would input a number for input_val and then global variables for squared_result and cubed_result that the function updates with the result. It would then theoretically work like:
#Declare global variables
b = 0
c = 0
#then somewhere in the code, call the function
math_function(2, b, c)
#check the new values
print(b) #Output: b = 4
print(c) #Output: c = 8
This would allow me to use the function in different python scripts without having to worry about what order the results are returned in.
First: I am in no way advocating this.
You could use the globals builtin function to access a global variable by name:
def gtest(name,value):
globals()[name] = value
gtest('new_global','new_value')
print(new_global)
I am not advocating this too, I would like to hear everyone's thoughts, but this can be done -
my_global = [0, 0]
def my_math_func(x, g):
g[0] = x ** 2
g[1] = x ** 3
my_math_func(3, my_global)

Binding events to widget not working

I have a function, say foo and it's supposed to bind an event
def foo:
for x in widget_list:
widget.config(command = lambda: self.function(widget_list.index(x))
def function(index):
do something here....
print index
The widget list contains buttons, and whenever I click corresponding buttons, they're supposed to print to the IDLE their index, but what happens is that all the buttons I click end up printing the last index of widget_list. I'm guessing that while for iterates, the argument of the function also changes thus only the last index is preserved. Is there anyway to bind the previous indexes to the button?
The simplest fix is to make sure that your lambda is using the current value of x:
widget.config(command = lambda x=x: self.function(widget_list.index(x))
This forces x to be a default parameter to the function, and the default value will be the value of x at the time the lambda is defined.

Tkinter OptionMenu add a command to multiple OptionMenus

Basically I have a series of OptionMenus that are created in a loop, but is currently empty:
option_menu = []
for ii in range(jj):
option_menu.append([])
for ll in range(kk):
option_menu[ii].append(OptionMenu(frame,tkinter_text_var[ii][ll],''))
Then elsewhere I use a checkbox to set the values along the lines of:
for ii in range(jj):
for ll in range(kk):
option_menu[ii][ll]["menu"].add_command(label = name_from_box.get(), command = lambda: tkinter_text_var[ii][ll].set(name_from_box.get()))
This works to populate all of the OptionMenus properly, but when I select a value in any of the OptionMenus, it only sets option_menu[jj][kk] (i.e. that last one made).
So what have I done wrong?
This is a very common question involving closures. Look at the following example:
alist = [lambda : x for x in range(10) ]
print (alist[2]()) #9
print (alist[4]()) #9
The'll all be 9. Why? Because each lambda function refers to the variable x. x gets changed at every iteration through the loop, but they all still refer to the same object.
One way around this is to use a default argument. Default arguments are evaluated when the function is created, not when it is called.
alist = [lambda y=x: y for x in range(10) ]
print (alist[2]()) #2
print (alist[4]()) #4
(another way to do the same thing involves functools.partial which you'll see sometimes ...)
I often like to say -- "take care with closures". They can be a little tricky.

Categories