Python Tkinter - 未定义名称

时间:2013-07-10 20:32:13

标签: python tkinter

代码:

def createLetters(frame, startX, startY, width, height, spacing):

    alphabet = ["A", "B", "C", "D", "E", "F", "G", "H", "I", 
                "J", "K", "L", "M", "N", "O", "P", "Q", "R", 
                "S", "T", "U", "V", "W", "X", "Y", "Z"]

    def letterAction(letter):
        letter.destroy()

    for i in range(0, 26):

        if (i >= 9 and i <= 17):
            y = startY +  height + 2 * spacing
            x = startX + ((width + spacing) * (i - 9))

        elif (i >= 17):
            y = startY + 2 * height + 3 * spacing
            x = (width + spacing) / 2 + startX + ((width + spacing) * (i - 18))

        elif (i <= 8):
            y = startY + spacing
            x = startX + ((width + spacing) * i)

        exec(alphabet[i] + " = Button(" + frame + ", text = '" + alphabet[i] + "', command = letterAction(" + alphabet[i] + "))")
        exec(alphabet[i] + ".place(x = " + str(x) + ", y = " + str(y) + ", width = " + str(width) + ", height = " + str(height) + ")")

错误:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Python33\lib\tkinter\__init__.py", line 1442, in __call__
    return self.func(*args)
  File "E:\Hangman\hangmanTk.py", line 106, in playScreen
    createLetters("playFrame", 175, 250, 50, 50, 0)
  File "E:\Hangman\hangmanTk.py", line 95, in createLetters
    exec(alphabet[i] + " = Button(" + frame + ", text = '" + alphabet[i] + "', command = letterAction(" + alphabet[i] + "))")
  File "<string>", line 1, in <module>
NameError: name 'A' is not defined

我尝试使用循环创建多个tkinter按钮。我可以创建好按钮,但我似乎无法为它们创建回调。当我尝试时,它告诉我,我用于按钮的变量没有定义。我尝试添加&#34; exec(&#34; global&#34; + alphabet [i])&#34;上面我定义了按钮,但没有改变任何东西。

2 个答案:

答案 0 :(得分:2)

Using exec is almost always the wrong way to do it, no matter what "it" is.

And creating variables dynamically is almost always the wrong thing to do.

你的问题让这个问题成为了原因的完美例证。


只需为按钮创建dict映射名称:

buttons = {}

# ...

letter = alphabet[i]
buttons[letter] = Button(frame, text = letter, command = letterAction(letter))
buttons[letter].place(x = x, y = y, width = width, height = height)

如果你真的想将dict转储到locals()(或类似地,self.__dict__globals()或......),那是微不足道的。但你没有。您需要使用变量的唯一地方是letterAction函数。所以:

def createLetters(frame, startX, startY, width, height, spacing):

    alphabet = string.ascii_uppercase
    buttons = {}

    def letterAction(letter):
        buttons[letter].destroy()

    for i, letter in enumerate(alphabet):

        if (i >= 9 and i <= 17):
            y = startY +  height + 2 * spacing
            x = startX + ((width + spacing) * (i - 9))

        elif (i >= 17):
            y = startY + 2 * height + 3 * spacing
            x = (width + spacing) / 2 + startX + ((width + spacing) * (i - 18))

        elif (i <= 8):
            y = startY + spacing
            x = startX + ((width + spacing) * i)

        buttons[letter] = Button(frame, text = letter, command = letterAction(letter))
        buttons[letter].place(x = x, y = y, width = width, height = height)

但请注意,这是错误的。 command = letterAction(letter) - 无论是直接运行,还是通过exec运行,都会立即调用letterAction(letter),在创建按钮之前销毁按钮,然后返回None然后设置为command

您需要lambda: letterAction(letter)partial(letterAction, letter)来解决此问题。

另外,您无法编写代码以将按钮变量本身传递给letter,无论是现在还是以后,因为该变量尚不存在。您必须像上面一样将字母作为字符串传递。


但实际上,如果你考虑一下,你根本不需要这些按钮变量 - 无论是dict还是其他。你只需要一种方法将每个按钮绑定为自己的回调目标,对吗?有很多方法可以做到这一点,但显而易见的是一个类,继承或委托给Button(或者,在这种情况下,两者都没有,因为你不需要将它用作按钮,或者甚至在创作之后记住它。

虽然我们正在努力,但是让我们删除一些无关的内容,这样只会让事情更难以阅读并解决17似乎属于两个不同群体的问题......

class SelfDestructiveButton(object):
    def __init__(self, frame, letter, x, y, width, height):
        self.button = Button(frame, text=letter, command=self.command)
        self.button.place(x=x, y=y, width=width, height=height)
    def command(self):
        self.button.destroy()

def createLetters(frame, startX, startY, width, height, spacing):
    for i, letter in enumerate(string.ascii_uppercase):
        if 9 <= i <= 17:
            y = startY +  height + 2 * spacing
            x = startX + ((width + spacing) * (i - 9))
        elif i > 17:
            y = startY + 2 * height + 3 * spacing
            x = (width + spacing) / 2 + startX + ((width + spacing) * (i - 18))
        else:
            y = startY + spacing
            x = startX + ((width + spacing) * i)
        SelfDestructiveButton(frame, letter, x, y, width, height)

if 'J' <= letter <= 'R'可能更清晰,因为它是字母而不是你在调试时会看到的数字。

答案 1 :(得分:0)

第一次调用exec时的字符串评估为:

"A = Button(<frame>, text = 'A', command = letterAction(A))"

因此,在定义之前,您正在引用A(名称)。我猜你忘记了第二个alphabet[i]周围的单引号:

exec(alphabet[i] + " = Button(" + frame + ", text = '" + alphabet[i] + "', command = letterAction('" + alphabet[i] + "'))")

请注意,这会调用letterAction('A'),即'A'.destroy(),因为字符串没有AttributeError方法,所以会引发destroy()。什么是letterAction应该达到的目标?