You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 03:45:47