与词法作用域和for循环斗争



如何获得modify_attr()函数(如下)不是在下面的for循环中捕获/更新b的值?(简化版,出现在mainloop()中):

for b in range(x):
button = tk.Button(button_frame, text="<", command=lambda: current_instance.modify_attr(b, -1))
button.place(x=110, y=80 + 18 * b)
button = tk.Button(button_frame, text=">", command=lambda: current_instance.modify_attr(b, 1))
button.place(x=120, y=80 + 18 * b)

目标是生成两列按钮,并将每个按钮对绑定到一对(有点复杂的)函数(条件reduce_by_one/incree_by_one函数)。

  • x=我需要生成的按钮对的数量
  • current_instance= class instance
  • modify_attr=一个类方法接受两个参数(好吧,我猜三个,如果我们包括self)

我的理解(基于这个和我在过去几年中读到的其他东西)是这个问题是一个常见的问题。在它的核心,问题是b的所有值相对于modify_attr()最终等于len(x)(而不是在我打算将命令绑定到按钮时使用b的值)。结果是一系列按钮被正确定位(通过b值(s)在button.place),但都指向列表中的最后一个元素,他们应该修改。

我以前遇到过这个确切的问题,并且能够使用辅助函数来解决它。但由于某些原因,我无法在这里应用该解决方案(再次简化为清晰):

for b in range(len(the_list)):
def helper_lambda(c):
return lambda event: refresh_frame(the_list[c])
window.bind(b + 1, helper_lambda(b))

明白了吗?同样的问题,helper_lamdba工作起来很有魅力。现在,在这种情况下,我绑定了一个热键而不是一个按钮命令,但我就是不明白为什么它会有不同的工作方式。因为从根本上说,问题在于for循环,而不是其中的函数。但是当我在我的按钮循环中实现辅助函数时,它失败了。

下面是我应用helper策略的失败尝试:

for b in range(x):
def helper_lambda(c, modifier):
return lambda event: current_instance.modify_attr(c, modifier)
button = tk.Button(button_frame, text="<", command=lambda: helper_lambda(b, -1))
button.place(x=110, y=80 + 18 * b)
button = tk.Button(button_frame, text=">", command=lambda: helper_lambda(b, 1))
button.place(x=120, y=80 + 18 * b)

我做错了什么?还有,它为什么会这样呢?有人在for循环之外使用增量值吗?!

第二种方法可能只需要很少的更改:

for b in range(x):
def helper_lambda(c, modifier):
return lambda: current_instance.modify_attr(c, modifier)  # removed event argument
button = tk.Button(button_frame, text="<", command=helper_lambda(b, -1))
button.place(x=110, y=80 + 18 * b)
button = tk.Button(button_frame, text=">", command=helper_lambda(b, 1))
button.place(x=150, y=80 + 18 * b)

但是,您可以直接使用lambda而不使用辅助函数:

for b in range(x):
button = tk.Button(button_frame, text="<", command=lambda b=b: current_instance.modify_attr(b, -1))
button.place(x=110, y=80 + 18 * b)
button = tk.Button(button_frame, text=">", command=lambda b=b: current_instance.modify_attr(b, 1))
button.place(x=150, y=80 + 18 * b)

这种情况下,functools.partial是比lambda表达式更好的选择。

from functools import partial
for b in range(x):
button = tk.Button(button_frame, text="<", command=partial(current_instance.modify_attr, b, -1))
button.place(x=110, y=80 + 18 * b)
button = tk.Button(button_frame, text=">", command=partial(current_instance.modify_attr, b, 1))
button.place(x=120, y=80 + 18 * b)

partial接收b作为参数,而不是简单地捕获名称b以供以后使用。

最新更新