【问题标题】:Python tkinter button callback unexpected behaviourPython tkinter 按钮回调意外行为
【发布时间】:2013-04-05 20:36:00
【问题描述】:

我用 Python 制作了一个简单的“程序启动器”。我有一个制表符分隔的文本文件,目前只有:

记事本   c:\windows\notepad.exe
写 c:\windows\write.exe

程序读取文本文件并创建一个对象数组。每个对象都有一个 name 属性(例如 notepad)和一个 route 属性(例如 C:\windows\notepad.exe)。然后,对于每个对象,应在按钮上创建一个具有正确名称的按钮,单击该按钮应使用该路径执行正确的程序。

该程序几乎可以运行。实际上,对象数组的形成是正确的,因为 for 循环正确地打印出两个不同的程序名称和两个不同的路径。问题是两个按钮,虽然标记正确,但启动了写入程序!我相信问题出现在回调的某个地方,但我的 Python 知识还不足以解决这个问题!正如您从下面的代码中看到的那样,我尝试了一个“内联”回调,并定义了一个“runprog”函数。它们都给出相同的结果。

您的帮助将不胜感激。

import Tkinter as tk
import subprocess

class MyClass:
    def __init__(self, thename,theroute):
        self.thename=thename
        self.theroute=theroute

myprogs = []

myfile = open('progs.txt', 'r')
for line in myfile:
    segmentedLine = line.split("\t")
    myprogs.append(MyClass(segmentedLine[0],segmentedLine[1]))
myfile.close()

def runprog(progroute):
    print(progroute)
    subprocess.call([progroute])

root = tk.Tk()
button_list=[]

for prog in myprogs:
    print(prog.thename)
    print(prog.theroute)

    button_list.append(tk.Button(root, text=prog.thename, bg='red', command=lambda: runprog(prog.theroute)))
#    button_list.append(tk.Button(root, text=prog.thename, bg='red', command= lambda: subprocess.call(prog.theroute)))

# show buttons
for button in button_list:
    button.pack(side='left', padx=10)
root.mainloop()

【问题讨论】:

标签: python button callback tkinter


【解决方案1】:

将您的命令更改为如下所示:

tk.Button(..., command=lambda route=prog.theroute: runprog(route))

注意 lambda 如何有一个关键字参数,您可以在其中将默认值设置为要与此按钮关联的路由。通过给关键字 arg 一个默认值,您将这个值“绑定”到这个特定的 lambda。

另一种选择是使用functools.partial,许多人觉得它比 lambda 不那么令人生畏。这样,您的按钮将如下所示:

import functools 
...
tk.Button(..., command=functools.partial(runprog,route)

第三种选择是将“runprog”函数移到类而不是程序的主要部分。在这种情况下,问题会变得简单得多,因为每个按钮都专门绑定到一个唯一的对象。

tk.Button(..., command=prog.runprog)

【讨论】:

  • 感谢您的帮助,布莱恩。我已经尝试了您的第一个建议,并且 python 编译,但是当我单击第一个按钮(记事本)时,我得到了我在下面发布的长“tkinter 回调异常”错误。我尝试将记事本更改为 chrome,但我遇到了同样的问题!所以可能这个建议很棒,而且我的程序中还有其他问题。也许它不喜欢我数组中的第一个程序? (虽然当我循环通过时它能够很好地打印出来)。我现在将尝试您的第三个建议(将其移至课堂)
  • 万岁!!现在一切正常。事实上,Bryan,阵列会打印出来,但我仍然无法让你的任何建议起作用,这让我认为这可能是一个空白问题。我将 .strip() 添加到“分段线”的末尾。我正在使用您的第三个建议,这是最简单的。再次感谢。
【解决方案2】:

只需更改此行:

button_list.append(tk.Button(root, text=prog.thename, bg='red', command=lambda: runprog(prog.theroute)))

到:

button_list.append(tk.Button(root, text=prog.thename, bg='red',
          command= (lambda route:(lambda: runprog(route))) (prog.theroute)))

推理:当您创建 lambda 函数(或函数内的任何其他函数)时,它确实可以访问(在 Python 2 中,只读访问)外部函数范围内的变量。但是,它确实访问了该范围内的“实时”变量——当调用 lambda 时,从“prog”检索到的值将是当时“prog”的含义,在这种情况下,它将是最后一个“prog”您的列表(因为用户只会在整个界面构建后很久才会单击一个按钮)

此更改引入了一个中间作用域——另一个函数体,当前“prog”值被传递到其中——并且在表达式运行时将 prog.theroute 分配给“route”变量。对列表中的每个程序执行一次。作为实际回调的内部 lambda 确实使用中间作用域中的“路由”变量 - 它为循环的每次传递保存不同的值。

【讨论】:

  • 谢谢 jsbueno。我不确定我是否完全理解你的解释,我需要一些时间来思考。我已经做出了你建议的改变。 “写”按钮有效(和以前一样),但“记事本”按钮给了我这个我不明白的长错误:
  • Tkinter 回调 Traceback 中的异常(最近一次调用最后一次):调用中的文件“C:\Python27_32\lib\lib-tk\Tkinter.py”,第 1410 行return self.func(*args) 文件“C:\Users\Matt\Desktop\Python\program_launcher3.py”,第 28 行,在
  • button_list.append(tk.Button(root, text=prog.thename, bg='red', command= (lambda route:(lambda: runprog(route))) (prog.theroute) )) 文件“C:\Users\Matt\Desktop\Python\program_launcher3.py”,第 19 行,在 runprog subprocess.call([progroute]) 文件“C:\Python27_32\lib\subprocess.py”,第 493 行,在调用中返回 Popen(*popenargs, **kwargs).wait() 文件“C:\Python27_32\lib\subprocess.py”,第 679 行,在 init 中 errread, errwrite) 文件“C: \Python27_32\lib\subprocess.py", line 896, in _execute_child startupinfo) WindowsError: [Error 2] The system cannot find the file specifid
  • 不需要两个 lambda。这是一个过于复杂的解决方案。
  • 哦,是的,@bryan?所以 - 你的解决方案只是“更神奇”而不是“更简单” - 2 lambdas 明确发生了什么。你的通过在“func-defaults”中创建一个具有当前值的差异函数对象来冻结该值 - 我认为这并不简单,因为它是同样合乎逻辑的,但我认为新手更难理解什么真的是按照你的方式进行。也就是说,使用你的 rway 很好,我会在我的代码中使用它 - 但我认为没有理由对此答案投反对票。
猜你喜欢
  • 2013-04-11
  • 2011-04-27
  • 2013-12-18
  • 2020-09-16
  • 2013-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多