1

代码:

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 按钮。我可以很好地创建按钮,但我似乎无法为它们创建回调。当我尝试时,它告诉我我用于按钮的变量没有定义。我尝试在我定义按钮的地方添加“exec(”global“+alphabet[i])”,但这并没有改变任何东西。

4

2 回答 2

2

exec无论“它”是什么,使用几乎总是错误的方式。

动态创建变量几乎总是错误的做法。

你让这个工作的问题完美地说明了为什么。


只需创建一个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)

如果您真的想将其转储dictlocals()(或,类似地,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,因为该变量尚不存在。您必须像我在上面所做的那样将letter作为字符串传递。


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

当我们这样做的时候,让我们删除一些无关紧要的括号,这样只会使事情更难阅读,并解决17似乎属于两个不同组的问题......</p>

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',因为它是您在调试时将看到的字母而不是数字。

于 2013-07-10T20:41:28.717 回答
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')ie ,因为字符串没有方法'A'.destroy(),所以会抛出一个。应该达到什么目的?AttributeErrordestroy()letterAction

于 2013-07-10T20:43:35.647 回答