for循环中的lambda只占用最后一个值

时间:2015-11-29 14:38:16

标签: python lambda tkinter contextmenu

版问题:

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

我尝试了什么:

#!/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

无论我选择哪三个选项。我认为它与垃圾收集有关,只有最后一个选项仍然存在,但我无法弄清楚如何避免这种情况。

建议任何帮助。

3 个答案:

答案 0 :(得分:7)

请阅读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在循环内发生变化,则需要进行相同的处理。

答案 1 :(得分: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,因此调用f[3]()的结果将为9,因为变量i的值位于迭代的末尾是9(在Python中,理解并不会为每次迭代创建新的绑定;它只是不断更新相同的绑定)。

在捕获时在Python中经常看到的“技巧”是所需的语义,即使用默认参数。在Python中,不同于C ++,默认值表达式是在函数定义时(即创建lambda时)而不是在调用函数时进行求值的。所以在像这样的代码中:

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

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

通常您会看到外观更加混乱的表格

lambda i=i: ...

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

答案 2 :(得分:-1)

我知道我来晚了,但是我发现了一个混乱的解决方法,可以完成工作(已在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语句。