for循环中的lambda只取最后一个值[重复]

Posted

技术标签:

【中文标题】for循环中的lambda只取最后一个值[重复]【英文标题】:lambda in for loop only takes last value [duplicate] 【发布时间】:2015-11-29 14:38:15 【问题描述】:

问题:

上下文菜单应动态显示过滤器变量并执行带有在回调中定义的参数的函数。 通用描述正确显示,但函数调用始终使用最后设置选项执行。

我尝试过的:

#!/usr/bin/env python

import Tkinter as tk
import ttk
from TkTreectrl import MultiListbox

class SomeClass(ttk.Frame):
    def __init__(self, *args, **kwargs):
        ttk.Frame.__init__(self, *args, **kwargs)
        self.pack(expand=True, fill=tk.BOTH)

        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

        self.View=MultiListbox(self)

        __columns=("Date","Time","Type","File","Line","-","Function","Message")
        self.View.configure(columns=__columns, expandcolumns=(0,0,0,0,0,0,0,1))

        self.View.bind("", self.cell_context)
        self.View.grid(row=0, column=0, sticky=tk.NW+tk.SE)

        self.__recordset          = []
        self.__recordset_filtered = False

        #Some dummy values
        self.__recordset.append(["Date", "Time", "INFO", "File", "12", "-", "Function", "Message Info"])
        self.__recordset.append(["Date", "Time", "DEBUG", "File", "12", "-", "Function", "Message Info"])
        self.__recordset.append(["Date", "Time", "WARNING", "File", "12", "-", "Function", "Message Info"])

        self.__refresh()

    def cleanView(self):
        self.View.delete(0, tk.END)

    def __refresh(self):
        self.cleanView()
        for row in self.__recordset:
            self.View.insert(tk.END, *row)

    def filter_records(self, column, value):
        print("Filter Log Recordset by column and value".format(**locals()))
        # Filter functionality works as expected
        # [...]

    def cell_context(self, event):
        __cMenu=tk.Menu(self, tearoff=0)

        if self.__recordset_filtered:
            __cMenu.add_command(label="Show all", command=lambda: filter_records(0, ""))

        else:
            column=2
            options=["INFO", "WARNING", "DEBUG"]

            for i in range(len(options)):
                option=options[i]
                __cMenu.add_command(label="".format(option), command=lambda: self.filter_records(column, option))
            # Also tried using for option in options here with same result as now
        __cMenu.post(event.x_root, event.y_root)

if __name__=="__main__":
    root=tk.Tk()
    app=SomeClass(root)
    root.mainloop()

我得到的当前输出是:

按 2 和 DEBUG 过滤日志记录集

无论我选择三个选项中的哪一个。我认为它与垃圾收集有关,只剩下最后一个选项,但我不知道如何避免这种情况。

建议任何帮助。

【问题讨论】:

【参考方案1】:

请阅读minimal examples。在不阅读您的代码的情况下,我相信您已经遇到了前面问题和答案中解决的一个众所周知的问题,需要 2 行来说明。函数执行时会计算函数体中的名称。

funcs = [lambda: i for i in range(3)]
for f in funcs: print(f())

打印 '2' 3 次,因为 3 个函数是相同的,并且每个函数中的 'i' 直到调用时才计算,此时 i == 2。但是,

funcs = [lambda i=i:i for i in range(3)]
for f in funcs: print(f())

创建三个不同的函数,每个函数都有不同的捕获值,因此会打印 0、1 和 2。在你的陈述中

__cMenu.add_command(label="".format(option),
    command=lambda: self.filter_records(column, option))

: 之前添加option=option 以捕获option 的不同值。你可能想重写为

lambda opt=option: self.filter_records(column, opt)

将循环变量与函数参数区分开来。如果column 在循环内发生变化,则需要进行相同的处理。

【讨论】:

【参考方案2】:

Python 中的闭包捕获变量,而不是值。例如考虑:

def f():
    x = 1
    g = lambda : x
    x = 2
    return g()

您希望调用f() 的结果是什么?正确答案是 2,因为 lambda f 捕获了变量 x,而不是它在创建时的值 1。

现在如果我们写:

L = [(lambda : i) for i in range(10)]

我们创建了一个包含 10 个不同 lambda 的列表,但它们都捕获了相同的变量 i,因此调用 L[3]() 结果将是 9,因为在迭代结束时变量 i 的值是 @ 987654329@(在 Python 中,推导不会为每次迭代创建新的绑定;它只是不断更新相同的绑定)。

在捕获 时在 Python 中经常可以看到的“技巧”是使用默认参数。在 Python 中,与 C++ 不同,默认值表达式是在函数定义时(即创建 lambda 时)而不是在调用函数时计算的。所以在代码中:

L = [(lambda j=i: j) for i in range(10)]

我们声明了一个参数j,并将创建lambda 时i 的当前值设置为默认值。这意味着当调用例如L[3]() 这次的结果是 3,因为“隐藏”参数的默认值(调用 L[3](42) 当然会返回 42)。

更多时候你会看到看起来更混乱的表格

lambda i=i: ...

其中“隐藏”参数与我们要捕获其值的变量同名。

【讨论】:

【参考方案3】:

我知道我迟到了,但我发现了一个可以完成工作的混乱解决方法(在 Python 3.7 中测试)

如果您使用双 lambda(就像我说的那样,非常混乱),您可以保留该值,如下所示:

第 1 步:创建嵌套 lambda 语句:

send_param = lambda val: lambda: print(val)

第 2 步:使用 lambda 语句:

send_param(i)

send_param 方法返回最内层的 lambda (lambda: print(val)) 而不执行语句,直到您调用不带参数的 send_param 的结果,例如:

a = send_param(i)
a()

只有第二行会执行print 语句。

【讨论】:

Python 中的函数调用通常很慢,而其他解决方案使用的变量赋值通常很快。在循环内分配一个临时变量也比嵌套 lambdas 和双重间接更简单。

以上是关于for循环中的lambda只取最后一个值[重复]的主要内容,如果未能解决你的问题,请参考以下文章

for循环中的lambda只取最后一个值[重复]

for循环仅检查向量中的最后一个值以使用R检查SQL Server中的重复项?

Python lambda不记得for循环中的参数[重复]

生成器与for循环的纠葛

带有指针值的 Go 通道在 for 循环中的行为不符合预期[重复]

Python:将 lambda 函数附加到列表 [重复]