for 循环中的 lambda 只取最后一个值
lambda in for loop only takes last value
问题集:
上下文菜单应该动态显示过滤器变量并执行一个函数,其中包含在回调中定义的参数。
通用描述正确显示,但函数调用始终使用最后设置的选项执行。
我试过的:
#!/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()
我得到的当前输出是:
Filter Log Recordset by 2 and DEBUG
无论我选择三个选项中的哪个。我认为它与垃圾收集有关,只剩下最后一个选项,但我不知道如何避免这种情况。
推荐任何帮助。
请阅读 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
在循环内改变,它需要同样的处理。
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
的值在迭代结束时是 9
(在 Python 中,理解不会为每次迭代创建新的绑定;它只是不断更新相同的绑定)。
在捕获 值 时经常在 Python 中看到的 "trick" 是期望的语义,即使用默认参数。在 Python 中,与 C++ 不同,默认值表达式在函数定义时(即创建 lambda 时)计算,而不是在调用函数时计算。所以在代码中:
L = [(lambda j=i: j) for i in range(10)]
我们正在声明参数 j
并将创建 lambda 时 i
的当前值设置为默认值。这意味着当调用例如L[3]()
这次结果将是 3,因为 "hidden" 参数的默认值(当然调用 L[3](42)
将 return 42)。
您经常会看到更加混乱的形式
lambda i=i: ...
其中 "hidden" 参数与我们要捕获其值的变量同名。
我知道我迟到了,但我发现了一个可以完成工作的混乱解决方法(在 Python 3.7 中测试)
如果你使用双 lambda(就像我说的,非常混乱)你可以保留值,像这样:
第 1 步:创建嵌套的 lambda 语句:
send_param = lambda val: lambda: print(val)
第 2 步:使用 lambda 语句:
send_param(i)
send_param
方法 returns 最内层的 lambda (lambda: print(val)
) 不执行语句,直到调用不带参数的 send_param
的结果,因为示例:
a = send_param(i)
a()
只有第二行会执行print
语句。
问题集:
上下文菜单应该动态显示过滤器变量并执行一个函数,其中包含在回调中定义的参数。 通用描述正确显示,但函数调用始终使用最后设置的选项执行。
我试过的:
#!/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()
我得到的当前输出是:
Filter Log Recordset by 2 and DEBUG
无论我选择三个选项中的哪个。我认为它与垃圾收集有关,只剩下最后一个选项,但我不知道如何避免这种情况。
推荐任何帮助。
请阅读 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
在循环内改变,它需要同样的处理。
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
的值在迭代结束时是 9
(在 Python 中,理解不会为每次迭代创建新的绑定;它只是不断更新相同的绑定)。
在捕获 值 时经常在 Python 中看到的 "trick" 是期望的语义,即使用默认参数。在 Python 中,与 C++ 不同,默认值表达式在函数定义时(即创建 lambda 时)计算,而不是在调用函数时计算。所以在代码中:
L = [(lambda j=i: j) for i in range(10)]
我们正在声明参数 j
并将创建 lambda 时 i
的当前值设置为默认值。这意味着当调用例如L[3]()
这次结果将是 3,因为 "hidden" 参数的默认值(当然调用 L[3](42)
将 return 42)。
您经常会看到更加混乱的形式
lambda i=i: ...
其中 "hidden" 参数与我们要捕获其值的变量同名。
我知道我迟到了,但我发现了一个可以完成工作的混乱解决方法(在 Python 3.7 中测试)
如果你使用双 lambda(就像我说的,非常混乱)你可以保留值,像这样:
第 1 步:创建嵌套的 lambda 语句:
send_param = lambda val: lambda: print(val)
第 2 步:使用 lambda 语句:
send_param(i)
send_param
方法 returns 最内层的 lambda (lambda: print(val)
) 不执行语句,直到调用不带参数的 send_param
的结果,因为示例:
a = send_param(i)
a()
只有第二行会执行print
语句。