python - 在 Tkinter 中动态创建菜单。 ( lambda 表达式?)

标签 python lambda copy tkinter

<分区>

我有一个菜单按钮,单击它时应该会显示一个包含特定字符串序列的菜单。该序列中的确切字符串是什么,我们直到运行时才知道,因此弹出的菜单必须在那个时刻生成。这是我拥有的:

class para_frame(Frame):
    def __init__(self, para=None, *args, **kwargs):
        # ...

        # menu button for adding tags that already exist in other para's
        self.add_tag_mb = Menubutton(self, text='Add tags...')

        # this menu needs to re-create itself every time it's clicked
        self.add_tag_menu = Menu(self.add_tag_mb,
                                 tearoff=0,
                                 postcommand = self.build_add_tag_menu)

        self.add_tag_mb['menu'] = self.add_tag_menu

    # ...

    def build_add_tag_menu(self):
        self.add_tag_menu.delete(0, END) # clear whatever was in the menu before

        all_tags = self.get_article().all_tags()
        # we don't want the menu to include tags that already in this para
        menu_tags = [tag for tag in all_tags if tag not in self.para.tags]

        if menu_tags:
            for tag in menu_tags:
                def new_command():
                    self.add_tag(tag)

                self.add_tag_menu.add_command(label = tag,
                                              command = new_command)
        else:
            self.add_tag_menu.add_command(label = "<No tags>")

重要的部分是“if menu_tags:”下的内容——假设 menu_tags 是列表 ['stack', 'over', 'flow']。那么我想做的实际上是这样的:

self.add_tag_menu.add_command(label = 'stack', command = add_tag_stack)
self.add_tag_menu.add_command(label = 'over', command = add_tag_over)
self.add_tag_menu.add_command(label = 'flow', command = add_tag_flow)

其中 add_tag_stack() 定义为:

def add_tag_stack():
    self.add_tag('stack')

等等。

问题是,变量“tag”的值是“stack”,然后是值“over”等等,直到调用 new_command 时它才会被计算,此时变量“tag”只是“流量”。因此,无论用户点击什么,添加的标签始终是菜单上的最后一个标签。

我最初使用的是 lambda,我想也许像上面那样显式定义函数可能会更好。无论哪种方式都会出现问题。我试过使用变量“tag”的副本(使用“current_tag = tag”或使用复制模块),但这并没有解决问题。我不确定为什么。

我的思绪开始转向“eval”之类的东西,但我希望有人能想出一个不涉及这种可怕事情的聪明方法。

非常感谢!

(如果相关,Tkinter.__version__ 返回 '$Revision: 67083 $' 并且我在 Windows XP 上使用 Python 2.6.1。)

最佳答案

首先,你的问题与Tkinter无关;最好将它简化为一段简单的代码来演示您的问题,这样您就可以更轻松地进行试验。这是我试验过的您正在做的事情的简化版本。我用字典代替菜单,以便于编写一个小测试用例。

items = ["stack", "over", "flow"]
map = { }

for item in items:
    def new_command():
        print(item)

    map[item] = new_command

map["stack"]()
map["over"]()
map["flow"]()

现在,当我们执行它时,如您所说,我们得到:

flow
flow
flow

这里的问题是 Python 的作用域概念。特别是,for 语句没有引入新级别的作用域,也没有为 item 引入新的绑定(bind);因此它在循环中每次都更新相同的 item 变量,并且所有 new_command() 函数都引用相同的项目。

您需要做的是为每个 item 引入新级别的范围和新的绑定(bind)。最简单的方法是将它包装在一个新的函数定义中:

for item in items:
    def item_command(name):
        def new_command():
            print(name)
        return new_command

    map[item] = item_command(item)

现在,如果将其代入前面的程序,您将获得所需的结果:

stack
over
flow

关于python - 在 Tkinter 中动态创建菜单。 ( lambda 表达式?),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/728356/

相关文章:

python - 如何使用 python 打印耗时(以毫秒为单位)

python - 如何创建可通过多个文件更新和访问的变量

android - 使用 RxJava Async 时的循环推理

C# - 读取/复制/替换文本中的行

ios - 如何在 Swift 中将一个 View 复制到另一个 View

javascript - 在另一个二维数组中复制打乱数组时出现问题

python - python 代码中的 genfromtxt 查找缺失的列

python - 不明白为什么这段代码在不同版本的Python中给出不同的结果

c# - 混合扩展方法、泛型和 lambda 表达式

java - 重复项不会添加到列表中