Python Tkinter使用exec时控件文本开头出现'.!'的原因及修复
Tkinter 猜单词游戏exec操作控件文本自动加
.!前缀问题 问题现象
用Python Tkinter开发Hangman(猜单词)游戏时,预期逻辑为点击字母按钮后,若字母属于待猜单词就显示在界面对应位置,但实际运行出现异常:通过exec函数操作的控件,文本开头被自动添加.!前缀。网传该问题由mainloop()导致,问题复现代码如下:
from tkinter import * from tkinter import messagebox from generate_word import word #DEFAULT VALUES score = 0 count = 0 win_count = 2 WINDOW_BG = '#e5404e' WINDOW_SIZE = '1200x870+300+80' FONT = ('Arial', 40) #this is an example with only one button buttons = [['b1','a',80,740]] #Creating window and configurating it window = Tk() window.geometry(WINDOW_SIZE) window.title('Hangman') window.config(bg = WINDOW_BG) #generates all of the labels for the word def gen_labels_word(): label = Label(window, text = " ", bg = WINDOW_BG, font = FONT) label.pack( padx = 40,pady = (500,100),side = LEFT) label1 = Label(window, text = word[0], bg = WINDOW_BG, font = FONT) label1.pack( padx = 41,pady = (500,100),side = LEFT) x = 21 for var in range(1,len(word)): exec('label{}=Label(window,text="_",bg=WINDOW_BG,font=FONT)'.format(var)) exec('label{}.pack(padx = {}, pady = (500,100), side=LEFT)'.format(var,x)) x += 1 exec('label{} = Label(window, text = "{}", bg = WINDOW_BG, font = FONT)'.format(len(word),word[-1])) exec('label{}.pack( padx = {},pady = (500,100),side = LEFT)'.format(len(word), x+1)) gen_labels_word() #hangman (images) hangman = ['h0','h1','h2','h3','h4','h5','h6'] for var in hangman: exec(f'{var}=PhotoImage(file="{var}.png")') han = [['label0','h0'],['label1','h1'],['label2','h2'],['label3','h3'],['label4','h4'],['label5','h5'],['label6','h6']] for p1 in han: exec('{}=Label(window, bg = WINDOW_BG ,image={})'.format(p1[0],p1[1])) exec('label0.place(x = 620,y = 0)') # 原代码此处letters变量未定义 for var in letters: exec(f'{var}=PhotoImage(file="{var}.png")') for var in buttons: exec(f'{var[0]}=Button(window,bd=0,command=lambda: game_brain("{var[0]}","{var[1]}"),bg = WINDOW_BG,font=FONT,image={var[1]})') exec('{}.place(x={},y={})'.format(var[0],var[2],var[3])) def game_brain(button, letter): global count,win_count,score exec('{}.destroy()'.format(button)) if letter in word: for i in range(1,len(word)): if word[i] == letter: win_count += 1 exec(f'label{i}.config(text="{letter}")') if win_count == len(word): score += 1 messagebox.showinfo('GOOD JOB, YOU WON!\n GOODBYE!') window.destroy() else: count += 1 exec('label{}.destroy()'.format(count-1)) exec('label{}.place(x={},y={})'.format(count,620,0)) if count == 6: messagebox.showinfo('GAME OVER','YOU LOST!\nGOODBYE!') window.destroy() def EXIT(): answer = messagebox.askyesno('ALERT','Do you want to exit the game?') if answer == True: window.destroy() e1 = PhotoImage(file = 'exit.png') ex = Button(window,bd = 0,command = EXIT,bg = WINDOW_BG,font = FONT,image = e1) ex.place(x=1050,y=20) window.mainloop()
问题原因
该问题和mainloop()无关,核心是滥用exec动态生成变量导致的作用域错误:
- 在
gen_labels_word()函数内部通过exec创建label1、label2这类控件变量时,变量默认属于函数局部作用域,函数执行完毕后局部变量会被Python垃圾回收机制回收 - Tkinter控件对象被回收后,内部会自动给控件标识加上
.!前缀标记失效状态,后续在全局作用域通过exec调用这些已经失效的引用时,操作的就是带异常前缀的无效对象,自然无法正常修改文本 - 原代码还存在重复导入依赖、
letters变量未定义、首尾标签逻辑冗余重复的问题。
修复方法
彻底放弃用exec拼接变量名的写法,改用列表、字典这类容器统一存储控件引用,从根源规避作用域问题:
- 用列表存储所有单词位置的Label控件,不需要单独为首字符、尾字符写重复的创建逻辑
- 用字典存储字母按钮、Hangman状态图对应的Label控件,通过键值直接索引访问
- 所有控件创建后直接把引用存入容器,调用时直接取容器内的有效对象即可,全程不需要
exec
核心修改参考:
# 替换原来的gen_labels_word逻辑 word_labels = [] # 首空格标签 pad_label = Label(window, text = " ", bg = WINDOW_BG, font = FONT) pad_label.pack(padx=40, pady=(500,100), side=LEFT) # 统一生成所有单词字符标签 for idx, char in enumerate(word): lb = Label(window, text="_", bg=WINDOW_BG, font=FONT) lb.pack(padx=21+idx, pady=(500,100), side=LEFT) word_labels.append(lb) # 后续匹配到字母时直接索引列表修改即可,不需要exec # for i, char in enumerate(word): # if char == letter: # word_labels[i].config(text=letter)
内容的提问来源于stack exchange,提问作者Dimitar.N
相关产品推荐
相关产品推荐

