Python 在 for 循环中没有正确调用 lambda 函数
Python lambda function is not being called correctly from within a for loop
我正尝试在 Python 中使用 Tkinter 制作一个计算器。我使用 for 循环来绘制按钮,并且我正在尝试使用 lambda 函数,以便仅在按下时调用按钮的操作,而不是在程序启动时立即调用。但是,当我尝试这样做时,会抛出“列表索引超出范围”错误。
这是绘制按钮并设置它们的过程的相关部分:
# button layout
# ( ) AC **
# 7 8 9 /
# 4 5 6 *
# 1 2 3 -
# 0 . = +
# list that holds the buttons' file names in order
imgOrder = ["lpr", "rpr", "clr", "pow",
"7", "8", "9", "div",
"4", "5", "6", "mul",
"1", "2", "3", "sub",
"0", "dot", "eql", "add"];
# list containing the actual values assigned to buttons in order
charOrder = ["(", ")", "AC", "**",
"7", "8", "9", "/",
"4", "5", "6", "*",
"1", "2", "3", "-",
"0", ".", "=", "+"];
imgIndex = 0;
for rowi in range(1, 6):
for coli in range(4):
# fetch and store the img
img = PhotoImage(file="images/{}.gif".format(imgOrder[imgIndex]));
# create button
button = Button(self, bg="white", image=img,
borderwidth=0, highlightthickness=0, activebackground="white",
command=lambda: self.process(charOrder[imgIndex]));
# set button image
button.image = img;
# put the button in its proper row and column
button.grid(row=rowi, column=coli, sticky=N+S+E+W);
imgIndex += 1;
# pack the GUI
self.pack(fill=BOTH, expand=1);
# processes button presses
def process(self, button):
print("Button {button} pressed!");
# AC clears the display
if (button == "AC"):
# clear
self.display["text"] = "";
# otherwise, the number/operator can be tacked on the end
else:
self.display["text"] += button;
具体来说,抛出错误的那一行是带有 lambda 函数的那一行,但我不知道有什么方法可以只让按钮在点击时注册,而不是为每个按钮单独写出这个块。
这是典型的意外关闭案例。这是一个简化的例子:
funcs = []
for i in range(3):
funcs.append(lambda: i + 1)
for func in funcs:
print(func())
人们可能希望这会打印 1 2 3
,但它会打印 3 3 3
。原因是,lambda
是一个 闭包 ,关闭变量 i
(在其上下文中捕获它)。当我们执行函数时,变量 i
保留在循环中的最后一个值 (2
)。重申一下,lambda
不捕获 值 ,而是 变量 。为避免这种情况,我们希望将 i
的当前值作为 参数 传递给函数。为此,我们可以构造一个接受 i
作为参数的函数,将 参数 捕获到闭包中,并 returns 我们想要的“自定义”函数:
from functools import partial
funcs = []
for i in range(3):
funcs.append((lambda val: lambda: val + 1)(i))
for func in funcs:
print(func())
等价地,我们可以使用 functools.partial
,它就是这样做的:
from functools import partial
funcs = []
for i in range(3):
funcs.append(partial(lambda val: val + 1, i))
for func in funcs:
print(func())
这里,lambda val: val + 1
需要一个参数; partial(lambda val: val + 1, 0)
将生成一个新函数,其中第一个参数固定为 0
- 基本上是 lambda: 0 + 1
。这样就没有捕获变量,从而避免了你遇到的问题。
tl;博士:
command=partial(lambda i: self.process(charOrder[i]), imgIndex)
问题是双循环结束时的变量imgIndex
是20。lambda表达式将self.process(charOrder[imgIndex])
绑定到变量,没有到它的值,所以在循环的出口,它的计算结果将是 self.process(charOrder[20])
.
一种解决方法是使用 lambda imgIndex=imgIndex: self.process(charOrder[imgIndex])
,它将按值传递参数。
我正尝试在 Python 中使用 Tkinter 制作一个计算器。我使用 for 循环来绘制按钮,并且我正在尝试使用 lambda 函数,以便仅在按下时调用按钮的操作,而不是在程序启动时立即调用。但是,当我尝试这样做时,会抛出“列表索引超出范围”错误。
这是绘制按钮并设置它们的过程的相关部分:
# button layout
# ( ) AC **
# 7 8 9 /
# 4 5 6 *
# 1 2 3 -
# 0 . = +
# list that holds the buttons' file names in order
imgOrder = ["lpr", "rpr", "clr", "pow",
"7", "8", "9", "div",
"4", "5", "6", "mul",
"1", "2", "3", "sub",
"0", "dot", "eql", "add"];
# list containing the actual values assigned to buttons in order
charOrder = ["(", ")", "AC", "**",
"7", "8", "9", "/",
"4", "5", "6", "*",
"1", "2", "3", "-",
"0", ".", "=", "+"];
imgIndex = 0;
for rowi in range(1, 6):
for coli in range(4):
# fetch and store the img
img = PhotoImage(file="images/{}.gif".format(imgOrder[imgIndex]));
# create button
button = Button(self, bg="white", image=img,
borderwidth=0, highlightthickness=0, activebackground="white",
command=lambda: self.process(charOrder[imgIndex]));
# set button image
button.image = img;
# put the button in its proper row and column
button.grid(row=rowi, column=coli, sticky=N+S+E+W);
imgIndex += 1;
# pack the GUI
self.pack(fill=BOTH, expand=1);
# processes button presses
def process(self, button):
print("Button {button} pressed!");
# AC clears the display
if (button == "AC"):
# clear
self.display["text"] = "";
# otherwise, the number/operator can be tacked on the end
else:
self.display["text"] += button;
具体来说,抛出错误的那一行是带有 lambda 函数的那一行,但我不知道有什么方法可以只让按钮在点击时注册,而不是为每个按钮单独写出这个块。
这是典型的意外关闭案例。这是一个简化的例子:
funcs = []
for i in range(3):
funcs.append(lambda: i + 1)
for func in funcs:
print(func())
人们可能希望这会打印 1 2 3
,但它会打印 3 3 3
。原因是,lambda
是一个 闭包 ,关闭变量 i
(在其上下文中捕获它)。当我们执行函数时,变量 i
保留在循环中的最后一个值 (2
)。重申一下,lambda
不捕获 值 ,而是 变量 。为避免这种情况,我们希望将 i
的当前值作为 参数 传递给函数。为此,我们可以构造一个接受 i
作为参数的函数,将 参数 捕获到闭包中,并 returns 我们想要的“自定义”函数:
from functools import partial
funcs = []
for i in range(3):
funcs.append((lambda val: lambda: val + 1)(i))
for func in funcs:
print(func())
等价地,我们可以使用 functools.partial
,它就是这样做的:
from functools import partial
funcs = []
for i in range(3):
funcs.append(partial(lambda val: val + 1, i))
for func in funcs:
print(func())
这里,lambda val: val + 1
需要一个参数; partial(lambda val: val + 1, 0)
将生成一个新函数,其中第一个参数固定为 0
- 基本上是 lambda: 0 + 1
。这样就没有捕获变量,从而避免了你遇到的问题。
tl;博士:
command=partial(lambda i: self.process(charOrder[i]), imgIndex)
问题是双循环结束时的变量imgIndex
是20。lambda表达式将self.process(charOrder[imgIndex])
绑定到变量,没有到它的值,所以在循环的出口,它的计算结果将是 self.process(charOrder[20])
.
一种解决方法是使用 lambda imgIndex=imgIndex: self.process(charOrder[imgIndex])
,它将按值传递参数。