Python lambda 函数未在 for 循环内正确调用[重复]

问题描述 投票:0回答:2

我正在尝试使用 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 函数的行,但我不知道有什么方法可以仅在单击时注册按钮,而不是单独为每个按钮写出此块。

python for-loop anonymous-function
2个回答
3
投票

这是一个典型的意外关闭案例。这是一个简化的示例:

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
的当前值作为 parameter 传递到函数中。为此,我们可以构造一个接受
i
作为参数的函数,将 parameter 捕获到闭包中并返回我们想要的“自定义”函数:

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)

1
投票

问题是双循环末尾的变量

imgIndex
是 20。lambda 表达式将
self.process(charOrder[imgIndex])
绑定到 变量,而不是它的值,因此在循环退出时它将计算为
 self.process(charOrder[20])

一种解决方法是使用

 lambda imgIndex=imgIndex: self.process(charOrder[imgIndex])
,它将按值传递参数。

© www.soinside.com 2019 - 2024. All rights reserved.