Passing functions as input to another function in another script (Python) - python

to avoid writing many times similar GUIs for my very basic python programs, I've written a GUI generator function in a standalone script like this:
def GUI_builder_function(entry_list, entry_default_list, button_list, function_list, panel_title, background_colour):
#Entries Generation
....
#Buttons Generation
for i in range(0, N_buttons,1):
button = tk.Button(text = button_list[i], command= (lambda i=i: eval(function_list[i].__name__ +"()")), bg = 'firebrick', fg = 'white', font = ('helvetica', 9, 'bold'))
input_data_panel.create_window(0.5*pos_width+300, 0.35*pos_height*(2*i+1) + 20, window = button)
buttons.append(button)
...
It's a very simple GUIgenerator which creates some entries and buttons corresponding to the input entry list and button list, and space them in a decent way.
Now, my problem is that: when the user clicks the generic i-th button, it must execute the function function_list[i] that the caller function has sent to the gui builder function.
However, despite the gui builder function has received function_list[i] as an input, it cannot execute it when the button is pressed. The python engine says:
"Name 'function name' is not defined".
Of course it is defined in the caller function. But it will be dirty to import each caller function of each program inside the common gui builder function. What I'm looking for is how to send to gui builder the function definition as an input to the gui builder function, which is in a standalone script as it must be utilized by different programs.

You just reference the function directly, if I understand the problem correctly, and assuming function_list is actually a list of functions rather than a list of strings representing function names.
def foo():
pass
def bar():
pass
function_list = [foo, bar]
...
button = tk.Button(..., command= function_list[i], ...)

Related

Tkinter: processing button clicks using lambda / eval

The following is the simplest code required to illustrate my question:
def function_one():
pass
def function_two():
pass
def button_clicked(function_name_as_string):
# do stuff common to all button clicks
# here...
# then respond to specific button click by converting the
# supplied function name string into an actual function which runs.
eval(function_name_as_string + "()")
button_one = Button(root,text="Run Function One",
command=lambda: button_clicked("function_one"))
button_two = Button(root,text="Run Function Two",
command=lambda: button_clicked("function_two"))
This is an attempt to make my tkinter code as compact as possible. Here, there are multiple buttons. But rather than write a function to handle each button click, I wanted a single function to handle all button clicks. In particular, that's because there are some operations I want to execute in response to any button click, before responding to the specific button click. At the same time, I wanted to eliminate any lengthy if/elif statement in the button_clicked function, which would be required for the function to know which button had been clicked, and take appropriate action.
In my solution above, when a button is clicked, the command keyword calls my button_clicked function, but also uses lambda to send the name of the function I want to run in response to the button click. The button_clicked function executes the code common to all button clicks, then uses eval to convert the supplied function name (passed as a string) into an actual function that now runs.
So the button_clicked function is not aware of which button was clicked. Rather, it is aware of what function ought to run in response to the specific button click. And this code runs perfectly.
I have never used lambda before, nor have I used eval. I came across both during a long search. But because both are new to me, I am a bit wary. My question is simply this: is the above approach correct and sound? Or are there hidden pitfalls that I may not be aware of? Thanks in advance.
I would argue it's better to pass the actual function than the function name as a string. eval rarely adds any value over other solutions, and mostly just makes the code harder to understand.
def function_one():
pass
def function_two():
pass
def button_clicked(function):
# do stuff common to all button clicks
# here...
# then respond to specific button click by calling
# the specified function
function()
button_one = Button(root,text="Run Function One",
command=lambda: button_clicked(function_one))
button_two = Button(root,text="Run Function Two",
command=lambda: button_clicked(function_two))

There is a way to wait on a user's answer in tkinter?

I'm developing an application what have its functions set in different files.
The main file have a tkinter interface and the buttons, entrys and labels are in other file, like this:
Mainfile.py
from tkinter import *
class Program:
def __init__(self, root):
root.geometry('200x200')
self.main_frame = Frame(root)
self.main_frame.pack()
import Buttons
self.branch = Buttons.New_Button(self.main_frame)
#Here i wuold like to verify the hipotetic variable after the main_frame were destroyed
if self.branch.hipotetic_variable:
root.mainloop()
app = Program(Tk())
Buttons.py
from tkinter import *
import functools
class New_Button:
def __init__(self, using_frame):
self.button_1 = Button(using_frame, text = 'Button 1', command=functools.partial(self.Func, using_frame))
self.button_1.pack()
def Func(self, to_destroy):
to_destroy.destroy()
#Here is the hipotetic variable what i would like to verify with if statment
self.hipotetic_variable = True
The problem is that I want to keep managing the program in the main file calling the other functions and implementing it, but I cannot verify if it's time to update the screen because mainloop makes impossible to verify it using a while loop and an hipotetic variable that's created after user pressed button.
I wold like to know if there is an way to update an variable contained in the Buttons.py file on Mainfile.py to keep implementing all other canvas in this file.
Your if self.branch.hipotetic_variable: check in the Program.__init__() method is only going to be executed when the Program class instance gets created initially, which is before the button that could change the value of the variable could have been pressed. You also don't want to make the hipotetic_variable an attribute of the Button because that will be destroyed along with the Frame it is in when that's destroyed in the button callback function.
Tkinter applications are user-event driven, meaning that they're "run" by responding to events (that's what mainloop is all about). This type of programming paradigm is different from the procedural or imperative one you're probably used to.
Therefore to do what you want requires setting things up so an event that the program can respond to will be generated, which in this case to when the frame is destroyed. One way to do that is by taking advantage of tkinter Variable classes to hold this hipotetic variable you're interested in. It looks like a boolean, so I used a tkinter BooleanVar to hold its value. One interesting thing about Variables is that you can have changes to their values "traced" by defining functions to be called whenever that happens. That's what I have done in the code below, and the callback function in this case — check_hipotetic_variable() — updates a Label to display the new value of the variable when it's called.
Below is your code with the modifications necessary to use a tkinter BooleanVar and trace changes to its value.
Mainfile.py
from tkinter import *
import Buttons
class Program:
def __init__(self, root):
root.geometry('200x200')
self.main_frame = Frame(root)
self.main_frame.pack()
self.notice_lbl = Label(root, text='')
self.notice_lbl.pack(side=BOTTOM)
self.hipotetic_variable = BooleanVar(value=False)
# Set up a trace "write" callback for whenever its contents are changed.
self.hipotetic_variable.trace('w', self.check_hipotetic_variable)
self.branch = Buttons.New_Button(self.main_frame, self.hipotetic_variable)
root.mainloop()
def check_hipotetic_variable(self, *args):
"""Display value of the hipotetic variable."""
value = self.hipotetic_variable.get()
self.notice_lbl.config(text=f'hipotetic variable is: {value}')
app = Program(Tk())
Buttons.py
from tkinter import *
import functools
class New_Button:
def __init__(self, using_frame, variable):
self.button_1 = Button(using_frame, text = 'Button 1',
command=functools.partial(self.Func, using_frame))
self.button_1.pack()
self.variable = variable # Save for use in callback.
def Func(self, to_destroy):
to_destroy.destroy()
self.variable.set(True) # # Change value of the variable.
P.S. I noticed you're not following the PEP 8 - Style Guide for Python Code, which makes reading your code harder to read and follow that if you're were following them — for that reason I strongly suggest you read the guide and start following the suggestions, especially the Naming Conventions which apply to functions and variable names, as well as the names of script files.

How to control the events of clicking on a button in tkinter

I want to ask that if I click on a button, then it perform a command and if I click again on the button then it perform another command. Basically I have two commands which I want to use in loop.
#Import tkinter here
from tkinter import *
#defining root function.
root = Tk()
root.geometry('200x100')
#define fuction for labelA
def funcA():
labelA["text"] = "A responds"
#define fuction for labelB
def funcB():
labelB["text"] = "B responds"
#Define button.
button = Button(root, text = 'Click Me', command=lambda:[funcA(), funcB()])
button.pack()
#creating both label A and B
labelA = Label(root, text="A")
labelB = Label(root, text="B")
labelA.pack()
labelB.pack()
root.mainloop()
In this code when I click on button, both the function run at same time. But I want that when i click on the button, the first function should run and if I click again on button then run second function. This will be in loop (like first of all first label will update then second label will update then again first label update then again second label).
You should have a bool value tracking if the button has been pressed or not. You can change this value in your functions as well.
somebool = False
#define fuction for labelA
def funcA():
if somebool:
labelA["text"] = "A responds"
#define fuction for labelB
def funcB():
if !somebool:
labelB["text"] = "B responds"
Note that this approach is not good if you want to access one function alone from another button. If you have a bool state like this though, you do not need 2 separate functions. You can make an event handler for the button and then call funcA or funcB from there.
somebool = False
def handler():
funcA() if somebool else funcB()
def funcA():
labelA["text"] = "A responds"
def funcB():
labelB["text"] = "B responds"
button = Button(root, text = 'Click Me', command=handler)
If you don't care about reusing the functions, change the command attribute for the button in your function to run the other one.
IIUC you want to alternate between two (or more) functions when clicking a button. So you can use itertools.cycle (built-in) to create an iterable that will loop forever over the given items in a tuple or list or sth else. So you add the two functions to the cycle and the button calls a third function that just gets the next item in the cycle and calls it and so on. (also I used just one label so that change is always visible since in your case you wouldn't see any change, but I believe you can adapt these functions for your needs):
import tkinter as tk
import itertools
def func_a():
label.config(text='a responds')
def func_b():
label.config(text='b responds')
toggle_funcs = itertools.cycle((func_a, func_b))
def toggle():
func = next(toggle_funcs)
func()
root = tk.Tk()
label = tk.Label(root, text='None')
label.pack()
btn = tk.Button(root, text='Click Me', command=toggle)
btn.pack()
root.mainloop()
Also:
I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.
I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case, class names in CapitalCase. Have two blank lines around function and class declarations. Object method definitions have one blank line around them.
i think you can save the button inside a variable and when function1 run after execution set command attribute to function2 and after function2 executed set command to function1

Understanding function call for tkinter button command

I am working through a tutorial on tkinter. I adapted some code, with the idea that every time you clicked on the button we'd create a new text label in the row below. What is confusing me is that when I write command=click it works, but when I write command = click() the function is executed a single time at the beginning of the script but not when the button is pressed. The first behaves as I'd expect. The second behaviour confuses me, as I would expect it to raise an error not perform the function once without pressing the button.
from tkinter import *
root = Tk()
position = {"Loc":1}
def click(position=position):
Label(root, text="ta").grid(row=position["Loc"], column=0)
position["Loc"] = position["Loc"] + 1
return
but = Button(root, text="click here to do nothing",command=click())
but.grid(row=0,column=0)
root.mainloop()
Note: I found the answer below more helpful than the answer to the related question here , because it made explicit that arguments are evaluated first before being passed into the function.
When you run a function in Python (like but = Button(root, text="click here to do nothing",command=click())), first all of the arguments get evaluated and then passed to the function. So, you are calling click() and then assigning its return value to the command. If it returns None or something else that Button can handle you will get no error. When you pass just click, you actually tell the function that it is supposed to call that function whenever you click the button.
Some simple code to clarify the issue:
def foo(bar):
print("\tCalling function {} from foo".format(bar))
if bar:
bar()
def fun():
print("\tExecuting fun function")
print("\nPassing function")
foo(fun)
print("\nPassing function return value")
foo(fun())
If you check out the outputs you will notice that they get executed in different order - in the second case, fun is executed first, returns None and than that None value is given to foo as a bar argument.
Passing function
Calling function <function fun at 0x000001A79F8863A0> from foo
Executing fun function
Passing function return value
Executing fun function
Calling function None from foo
As to why there is no error - why would there be?
Both are valid statements. You could have a function that returns other function and pass it to the button that way.

Keep unique variable in for loop widget command

Im adding/removing strings from an optionmenu widget and i've learned that you have to run a command or function when a string is added that sets the widget variable to that string. This is done automatically for strings provided when the widget is originally created.
from tkinter import *
def _print(var, string):
print(string)
var.set(string)
lst = ['hello', 'bob', 'testing']
main = Tk()
var = StringVar()
options = OptionMenu(main, var, 'option1')
options.grid()
for string in lst:
options['menu'].add_command(label=string, command=lambda: _print(var, string))
main.mainloop()
As you click each option in the menu it should print the string on the widget so you know you have it selected. The problem is that the for loop is setting all of the commands to use the same variable. It looks like its just not keeping a unique variable for each line that is run. I've tried lambda event, i=string: _print(var, i) but even if I set the _print function to receive an event lambda always says its missing an event. Maybe i'm trying to do the i=string part wrong or it cant be done this way.
How can I keep the variable "string" unique for each add_command line that is run?

Categories

Resources