如何通过按钮将变量传递给函数?
How to pass a variable to a function via a Button?
我在名为 script.py
的模块中定义了以下函数
def load_file():
file_name = filedialog.askopenfile(filetypes=['EXE files', '*.exe'])
return file_name
def simulation(input_path, input_par): # this works fine
# the functionruns an EXE file ang gives me two lists lst1 and lst2
return lst1, lst2
现在 main.py
中定义的以下按钮调用 load_file
函数并且它工作正常(我从终端中的函数内打印路径)。我无法弄清楚的是如何通过 main.py
.
中的按钮将我在 load_file
函数中获得的路径作为参数 input_path
传递给函数模拟
input_var = #??
button = Button(frame_file, text='Open file', command=load_file)
button.grid(row=1, column=1)
simulation(input_path, input_var)
您可以使用partial
方法来传递参数。
simulation = partial(simulation, input_path, input_par)
buttonCal = tk.Button(root, text="Simulate", command=simulation)
我认为你的问题是重复的,它不是标题中的 apparent,因为它的措辞有点奇怪:
How to return value from tkinter button callback
我可以提出一个不同于线程中的解决方案,但它确实不干净。
您可以使用 lambda 或已经提到的 partial
方法来传递可变 object,如 @vnk 所述。使用 lambdas(或部分应用函数)是将参数传递给 Tkinter 回调的常用方法。
因为我假设你的路径可能是一个不可变的字符串,你应该使用 object 例如 pathlike.Path,或者自己创建一个 object 并包装你的路径串进去。不幸的是,您的回调也必须为此配合。
path_container = [] # You could define a class to have cleaner code than having to use the magic 0 index.
tk.Button(root, text="Open file", command=(lambda: load_file(path_container)))
# Your load_file has to know that its argument is a list whose 0-th element must contain the path,
# or, if you opt for the more readable approach, that the .path attribute of your
# path container object should be used.
如果你真的很疯狂,你甚至可以创建一个函数来改变一个可变的 object,这样你就不必改变你的 load_file
函数:
def store_path(path_container, path):
path_container[0] = path # Or path_container.path = path, if you're doing it cleaner.
tk.Button(root, text="Open file", command=(lambda: store_path(path_container, load_file())))
lambda 很有用,因为它从 parent 范围捕获变量,例如 path_container
。
由于适用于 lambda 的限制,它们的 body 必须是单个表达式,而在 Python 中赋值(实际上,名称绑定)不是,因此需要一个助手。它有许多其他语言,例如 C.
老实说,最明智的解决方案可能是将回调设计为使用 pathlib.Path object。如果您的程序变大,即使您不使用 Tkinter,您也会受益于正确的路径 class 而不是字符串。
使用 tkinter
时常用的方法是使用 lambda
函数。棘手的部分是取回 return 值,因为 command=
函数的 return 值被忽略了。人们经常使用 global
变量来解决这个问题,但如果函数是在另一个模块中定义的,那么这种策略就不会起作用。幸运的是 tkinter
有几个特定类型的容器 Variable
subclasses 可以作为参数传递给允许从中检索值的函数。
这里的可运行代码显示了使用它们来执行此操作的一种方法:
script.py
from tkinter import filedialog
def load_file(input_path):
file_name = filedialog.askopenfilename(filetypes=[('EXE files', '*.exe')])
input_path.set(file_name)
def simulation(input_path, input_par): # this works fine
# the function runs an EXE file ang gives me two lists lst1 and lst2
return lst1, lst2
main.py
from script import load_file, simulation
import tkinter as tk
root = tk.Tk()
frame_file = tk.Frame(root)
frame_file.pack()
input_path = tk.StringVar()
input_var = 42
button = tk.Button(frame_file, text='Open file', command=lambda: load_file(input_path))
button.grid(row=1, column=1)
button2 = tk.Button(frame_file, text='Run simulation',
command=lambda: simulation(input_path.get(), input_var))
button2.grid(row=1, column=2)
root.mainloop()
更新
这里有一个稍微复杂但更灵活的版本,它适用于更多类型的 return 值,包括用户定义的 classes(不仅仅是四个 tkinter
支持 — bool、float、int、string)。自动支持更多类型的returned数据,也意味着调用的函数不需要修改。
它通过定义一个 CallBack
class 来做到这一点,其实例“包装”要调用的函数并将它们的 return 值保存在 tkinter StringVar
小部件中,因此稍后可以根据需要通过调用 class 的实例具有的方法 get_result()
来检索它们。为了使用更多数据类型,它使用 Python 的内置 pickle
模块来临时存储数据。
script2.py
from tkinter import filedialog
def load_file():
file_name = filedialog.askopenfilename(filetypes=[('EXE files', '*.exe')])
return file_name
def simulation(input_path, input_par): # this works fine
# the function runs an EXE file and gives me two lists lst1 and lst2
print(f'simulation({input_path=}, {input_par=}) called')
list1, list2 = ['param', input_par], list('spam')
return list1, list2
main2.py
from script2 import load_file, simulation
import pickle
import tkinter as tk
from tkinter import messagebox
class CallBack:
def __init__(self, func):
self.func = func
self.var = tk.StringVar()
def get_result(self):
pickled_result = self.var.get()
return pickle.loads(pickled_result.encode('latin1')) # Unpickle and return it.
def __call__(self):
res = self.func()
pickled_result = pickle.dumps(res).decode('latin1') # Pickle result.
self.var.set(pickled_result) # And save it in tkinter StringVar.
root = tk.Tk()
frame_file = tk.Frame(root)
frame_file.pack()
input_par = 42
callback1 = CallBack(load_file) # Wrap function from other module.
button = tk.Button(frame_file, text='Open file', command=callback1)
button.grid(row=1, column=1)
def run_sim():
""" Run simulation function and display value returned. """
# Wrap other function in other module to allow results to be retrieved.
callback2 = CallBack(lambda: simulation(callback1.get_result(), input_par))
callback2() # Call the function and save its return value.
results = callback2.get_result() # Get function's return value.
messagebox.showinfo(title='Simulation Results', message=str(results))
button2 = tk.Button(frame_file, text='Run simulation', command=run_sim)
button2.grid(row=1, column=2)
root.mainloop()
我在名为 script.py
def load_file():
file_name = filedialog.askopenfile(filetypes=['EXE files', '*.exe'])
return file_name
def simulation(input_path, input_par): # this works fine
# the functionruns an EXE file ang gives me two lists lst1 and lst2
return lst1, lst2
现在 main.py
中定义的以下按钮调用 load_file
函数并且它工作正常(我从终端中的函数内打印路径)。我无法弄清楚的是如何通过 main.py
.
load_file
函数中获得的路径作为参数 input_path
传递给函数模拟
input_var = #??
button = Button(frame_file, text='Open file', command=load_file)
button.grid(row=1, column=1)
simulation(input_path, input_var)
您可以使用partial
方法来传递参数。
simulation = partial(simulation, input_path, input_par)
buttonCal = tk.Button(root, text="Simulate", command=simulation)
我认为你的问题是重复的,它不是标题中的 apparent,因为它的措辞有点奇怪:
How to return value from tkinter button callback
我可以提出一个不同于线程中的解决方案,但它确实不干净。
您可以使用 lambda 或已经提到的 partial
方法来传递可变 object,如 @vnk 所述。使用 lambdas(或部分应用函数)是将参数传递给 Tkinter 回调的常用方法。
因为我假设你的路径可能是一个不可变的字符串,你应该使用 object 例如 pathlike.Path,或者自己创建一个 object 并包装你的路径串进去。不幸的是,您的回调也必须为此配合。
path_container = [] # You could define a class to have cleaner code than having to use the magic 0 index.
tk.Button(root, text="Open file", command=(lambda: load_file(path_container)))
# Your load_file has to know that its argument is a list whose 0-th element must contain the path,
# or, if you opt for the more readable approach, that the .path attribute of your
# path container object should be used.
如果你真的很疯狂,你甚至可以创建一个函数来改变一个可变的 object,这样你就不必改变你的 load_file
函数:
def store_path(path_container, path):
path_container[0] = path # Or path_container.path = path, if you're doing it cleaner.
tk.Button(root, text="Open file", command=(lambda: store_path(path_container, load_file())))
lambda 很有用,因为它从 parent 范围捕获变量,例如 path_container
。
由于适用于 lambda 的限制,它们的 body 必须是单个表达式,而在 Python 中赋值(实际上,名称绑定)不是,因此需要一个助手。它有许多其他语言,例如 C.
老实说,最明智的解决方案可能是将回调设计为使用 pathlib.Path object。如果您的程序变大,即使您不使用 Tkinter,您也会受益于正确的路径 class 而不是字符串。
使用 tkinter
时常用的方法是使用 lambda
函数。棘手的部分是取回 return 值,因为 command=
函数的 return 值被忽略了。人们经常使用 global
变量来解决这个问题,但如果函数是在另一个模块中定义的,那么这种策略就不会起作用。幸运的是 tkinter
有几个特定类型的容器 Variable
subclasses 可以作为参数传递给允许从中检索值的函数。
这里的可运行代码显示了使用它们来执行此操作的一种方法:
script.py
from tkinter import filedialog
def load_file(input_path):
file_name = filedialog.askopenfilename(filetypes=[('EXE files', '*.exe')])
input_path.set(file_name)
def simulation(input_path, input_par): # this works fine
# the function runs an EXE file ang gives me two lists lst1 and lst2
return lst1, lst2
main.py
from script import load_file, simulation
import tkinter as tk
root = tk.Tk()
frame_file = tk.Frame(root)
frame_file.pack()
input_path = tk.StringVar()
input_var = 42
button = tk.Button(frame_file, text='Open file', command=lambda: load_file(input_path))
button.grid(row=1, column=1)
button2 = tk.Button(frame_file, text='Run simulation',
command=lambda: simulation(input_path.get(), input_var))
button2.grid(row=1, column=2)
root.mainloop()
更新
这里有一个稍微复杂但更灵活的版本,它适用于更多类型的 return 值,包括用户定义的 classes(不仅仅是四个 tkinter
支持 — bool、float、int、string)。自动支持更多类型的returned数据,也意味着调用的函数不需要修改。
它通过定义一个 CallBack
class 来做到这一点,其实例“包装”要调用的函数并将它们的 return 值保存在 tkinter StringVar
小部件中,因此稍后可以根据需要通过调用 class 的实例具有的方法 get_result()
来检索它们。为了使用更多数据类型,它使用 Python 的内置 pickle
模块来临时存储数据。
script2.py
from tkinter import filedialog
def load_file():
file_name = filedialog.askopenfilename(filetypes=[('EXE files', '*.exe')])
return file_name
def simulation(input_path, input_par): # this works fine
# the function runs an EXE file and gives me two lists lst1 and lst2
print(f'simulation({input_path=}, {input_par=}) called')
list1, list2 = ['param', input_par], list('spam')
return list1, list2
main2.py
from script2 import load_file, simulation
import pickle
import tkinter as tk
from tkinter import messagebox
class CallBack:
def __init__(self, func):
self.func = func
self.var = tk.StringVar()
def get_result(self):
pickled_result = self.var.get()
return pickle.loads(pickled_result.encode('latin1')) # Unpickle and return it.
def __call__(self):
res = self.func()
pickled_result = pickle.dumps(res).decode('latin1') # Pickle result.
self.var.set(pickled_result) # And save it in tkinter StringVar.
root = tk.Tk()
frame_file = tk.Frame(root)
frame_file.pack()
input_par = 42
callback1 = CallBack(load_file) # Wrap function from other module.
button = tk.Button(frame_file, text='Open file', command=callback1)
button.grid(row=1, column=1)
def run_sim():
""" Run simulation function and display value returned. """
# Wrap other function in other module to allow results to be retrieved.
callback2 = CallBack(lambda: simulation(callback1.get_result(), input_par))
callback2() # Call the function and save its return value.
results = callback2.get_result() # Get function's return value.
messagebox.showinfo(title='Simulation Results', message=str(results))
button2 = tk.Button(frame_file, text='Run simulation', command=run_sim)
button2.grid(row=1, column=2)
root.mainloop()