简体   繁体   English

Python lambda function 没有从 for 循环中正确调用

[英]Python lambda function is not being called correctly from within a for loop

I am trying to make a calculator using Tkinter in Python.我正在尝试在 Python 中使用 Tkinter 制作计算器。 I use a for loop to draw the buttons in, and I'm trying to use lambda functions so the buttons' actions are only called when pressed instead of calling right when the program starts.我使用 for 循环来绘制按钮,并且我正在尝试使用 lambda 函数,因此按钮的操作仅在按下时调用,而不是在程序启动时调用。 When I attempt this, however, a "list index out of range" error is thrown.但是,当我尝试这样做时,会引发“列表索引超出范围”错误。

This is the relevant section which draws the buttons and sets their processes:这是绘制按钮并设置其流程的相关部分:

# 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;

Specifically the line that is throwing the error is the line with the lambda function, but I don't know of any way to only make the buttons register on click, short of writing out this block for every button individually.具体来说,引发错误的行是 lambda function 的行,但我不知道有什么方法可以只让按钮在点击时注册,没有为每个按钮单独写出这个块。

This is a classic case of unwanted closure.这是一个不受欢迎的关闭的经典案例。 Here's a simplified example:这是一个简化的示例:

funcs = []
for i in range(3):
    funcs.append(lambda: i + 1)
for func in funcs:
    print(func())

One might expect that this will print 1 2 3 , but it prints 3 3 3 instead.人们可能期望这将打印1 2 3 ,但它会打印3 3 3 The reason is, lambda is a closure , closing over the variable i (capturing it in its context).原因是, lambda是一个闭包,关闭变量i (在其上下文中捕获它)。 When we execute the functions, the variable i is left at its last value in the loop ( 2 ).当我们执行这些函数时,变量i保留在循环中的最后一个值( 2 )。 To reiterate, the lambda does not capture the value , but the variable .重申一下, lambda不捕获,而是捕获变量 To avoid this, we want to pass the current value of i into the function as a parameter .为了避免这种情况,我们希望将i的当前值作为参数传递给 function。 To do that, we can construct a function that accepts i as the parameter, captures the parameter into the closure and returns the "customised" function we want:为此,我们可以构造一个接受i作为参数的 function,将参数捕获到闭包中并返回我们想要的“定制” function:

from functools import partial
funcs = []
for i in range(3):
    funcs.append((lambda val: lambda: val + 1)(i))
for func in funcs:
    print(func())

Equivalently, we can use functools.partial , which does just this:等效地,我们可以使用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())

Here, lambda val: val + 1 will expect a parameter;在这里, lambda val: val + 1将期望一个参数; partial(lambda val: val + 1, 0) will produce a new function where the first parameter is fixed at 0 - basically, lambda: 0 + 1 . partial(lambda val: val + 1, 0)将产生一个新的 function ,其中第一个参数固定为0 - 基本上, lambda: 0 + 1 This captures no variables, and thus avoids the problem you encountered.这不捕获任何变量,从而避免了您遇到的问题。

tl;dr: tl;博士:

command=partial(lambda i: self.process(charOrder[i]), imgIndex)

The issue is that the variable imgIndex at the end of the double loop is 20. The lambda expression binds self.process(charOrder[imgIndex]) to the variable , no to its value, so at the exit of the loop it will evaluate to self.process(charOrder[20]) .问题是双循环结束时的变量imgIndex为 20。 lambda 表达式将self.process(charOrder[imgIndex])绑定到变量,而不绑定到它的值,因此在循环退出时它将评估为self.process(charOrder[20])

A way around is to use lambda imgIndex=imgIndex: self.process(charOrder[imgIndex]) , which will pass the parameter by value.一种解决方法是使用lambda imgIndex=imgIndex: self.process(charOrder[imgIndex]) ,它将按值传递参数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么从带有map和预定义函数的python文件中调用时,而不是通过带有map和lambda表达式的cli调用时,timeit较慢? - Why is timeit slower when called from a python file with map and a predefined function over being called via cli with map and a lambda expression? 如何防止 function 在 while 循环中被多次调用 - PYTHON - How to prevent a function from being called more than one time in a while loop - PYTHON 为什么我的 function 第二次从 python while 循环中调用时崩溃? - Why does my function crash the second time it's called from within a python while loop? 如何从循环内正确修改python中循环的迭代器 - how to correctly modify the iterator of a loop in python from within the loop 查看从 function 中调用的所有函数的代码 - View the code of all the functions being called from within a function 确定在lambda函数中调用的方法 - Determining the method called within a lambda function Python在函数内使用Lambda - Python Using Lambda Within a Function Python, Function 没有被随机调用 - Python, Function is not being called randomly 无法从 python aws lambda 函数中运行二进制文件 - Can't run binary from within python aws lambda function For 循环嵌套在 For 循环中,在 for 循环中调用 function 将列表恢复为原始值,Python - For loop nested in For loop, called function within for loop reverts list back to orginal value, Python
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM