如何通过按钮将变量传递给函数?

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()