我有一个menubutton,点击它时会显示一个包含特定字符串序列的菜单。在该序列中究竟是什么字符串,我们直到运行时才知道,因此必须在那一刻生成弹出的菜单。这就是我所拥有的:
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。)
答案 0 :(得分:7)
首先,你的问题与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
的新绑定;所以它每次循环都会更新相同的item
变量,并且所有new_command()
函数都引用同一个项目。
您需要做的是为每个item
引入新的范围,并使用新的绑定。最简单的方法是将其包装在新的函数定义中:
for item in items:
def item_command(name):
def new_command():
print(name)
return new_command
map[item] = item_command(item)
现在,如果您将其替换为前面的程序,您将获得所需的结果:
stack
over
flow
答案 1 :(得分:2)
我认为,这种事情在Tkinter中是一个非常普遍的问题。
试试这个(在适当的时候):
def new_command(tag=tag):
self.add_tag(tag)
答案 2 :(得分:0)
我有类似的错误。仅显示列表中的最后一项。通过设置
修复 command=lambda x=i: f(x)
请注意x=i
之后的lambda
。此分配使您的本地变量i
直接进入f(x)
的{{1}}函数。希望,这个简单的例子将有所帮助:
command