【问题标题】:Python Tkinter - RecursionError: maximum recursion depth exceeded while calling a Python objectPython Tkinter - RecursionError:调用 Python 对象时超出最大递归深度
【发布时间】:2017-02-04 20:36:27
【问题描述】:

这是一个我自己无法解决的奇怪问题。基本上我正在使用 python 3.6 和 Tkinter 制作扫雷。游戏运行良好,直到大约在一场艰难的游戏进行到一半时(如果它们被拖得更久,它也会发生在其他游戏中),您单击一个棋子,然后控制台中出现错误。过了一会儿,在能够再玩几个动作后,游戏就崩溃了。对不起,“临时”代码,这不是我最好的作品,因为它从未打算被任何人看到!

(我没有包含程序中的所有代码)

from tkinter import *
import random

root = Tk()

buttons = []

images = {"X": PhotoImage(file="Mine.gif"), "X/": PhotoImage(file="MineSlash.gif"), "F": PhotoImage(file="Flag.gif")}
colors = {1:"#0008f9", 2:"#10cc00", 3:"#ff4f1e", 4:"#4b00a8", 5:"#bc0000", 6:"#00cbdd"}

board = []

mines = []

buttons = []

minesLeft = 10

numMines = 10

boardSize = 8

dead = False

minesLeftLabel = Label(font="Verdana 10 bold")

startingButtons = []

        
def ButtonCreate():
    Button(root, text="Restart", width=8, height=1, font="Verdana 10 bold", command=Restart).grid(row=0, column=0, columnspan=3)
    global minesLeftLabel
    minesLeftLabel.grid(row=0, column=4, columnspan=2)
    minesLeftLabel.config(text=str(minesLeft))
    for y in range(boardSize):
        tempList = []
        for x in range(boardSize):
            button = Button(root, text=board[y][x], bg="#eaeaea", width=2, height=1, font="Verdana 10 bold")
            button.bind('<Button-1>', lambda event, x=x, y=y: MinePressed(x, y, False))
            button.bind('<Button-3>', lambda event, x=x, y=y: MinePressed(x, y, True))
            button.grid(row=y + 2, column=x)
            tempList.append(button)
        buttons.append(tempList)

def CheckNeighbours(x, y):
    buttons[y][x].config(text=" ", fg="grey", bg="#e5e5e5", relief=SUNKEN)
    for yPos in range(-1, 2):
        for xPos in range(-1, 2):
            if y + yPos >= 0 and y + yPos <= boardSize - 1 and x + xPos >= 0 and x + xPos <= boardSize - 1:
                if mines[y + yPos][x + xPos] != 0:
                    board[y + yPos][x + xPos] = mines[y + yPos][x + xPos]
                    buttons[y + yPos][x + xPos].config(text=board[y + yPos][x + xPos], fg=colors[board[y + yPos][x + xPos]], bg="#e5e5e5", width=2, height=1, relief=SUNKEN)
                elif board[y + yPos][x + xPos] == " " and mines[y + yPos][x + xPos] == 0:
                    board[y + yPos][x + xPos] = mines[y + yPos][x + xPos]
                    CheckNeighbours(x + xPos, y + yPos)
    return

def MinePressed(x, y, flag):
    if dead != True:
        if flag:
            global minesLeft
            global minesLeftLabel
            if board[y][x] == " ":
                board[y][x] = "F"
                minesLeft -= 1
                minesLeftLabel.config(text=str(minesLeft))
                buttons[y][x].config(image=images["F"], width=22, height=22)
            elif board[y][x] == "F":
                board[y][x] = " "
                minesLeft += 1
                minesLeftLabel.config(text=str(minesLeft))
                buttons[y][x].config(text=board[y][x], image="", width=2, height=1)
        else:
            if board[y][x] != "F":
                board[y][x] = mines[y][x]
                if board[y][x] == "X":
                    GameOver()
                    buttons[y][x].config(image=images["X"], bg="red", width=21, height=21, relief=SUNKEN)
                elif board[y][x] == 0:
                    CheckNeighbours(x, y)
                else:
                    buttons[y][x].config(text=board[y][x], fg=colors[board[y][x]], bg="#e5e5e5", relief=SUNKEN)
        root.update_idletasks()
        root.mainloop()

def Restart():
    ResetBoards()
    global dead
    dead = False
    global buttons
    buttons = []
    CreateMines()
    MineCalculations()
    CreateWindow()


def ResetBoards():
    global board
    board = []
    for y in range(boardSize):
        tempList = []
        for x in range(boardSize):
            tempList.append(" ")
        board.append(tempList)
    global mines
    mines = []
    for y in range(boardSize):
        tempList = []
        for x in range(boardSize):
            tempList.append(0)
        mines.append(tempList)

def BoardSize(i):
    global boardSize
    boardSize = i
    global numMines
    numMines = int(boardSize * boardSize * 0.18)
    global minesLeft
    minesLeft = numMines
    ResetBoards()
    CreateMines()
    MineCalculations()
    CreateWindow()

def CreateWindow():
    root.resizable(width=FALSE, height=FALSE)
    root.geometry('{}x{}'.format(28 * boardSize, 28 * (boardSize + 1)))
    for i in startingButtons:
        i.destroy()
    ButtonCreate()
    
def SelectSize():
    root.resizable(width=FALSE, height=FALSE)
    root.geometry('{}x{}'.format(150, 150))

    global startingButtons
    button1 = Button(text="Beginner", font="Verdana 10 bold", anchor=N, command=lambda i=8: BoardSize(i))
    button1.place(relx=0.5, rely=0.2, anchor=CENTER)
    button2 = Button(text="Intermediate", font="Verdana 10 bold", anchor=N, command=lambda i=16: BoardSize(i))
    button2.place(relx=0.5, rely=0.45, anchor=CENTER)
    button3 = Button(text="Hard", font="Verdana 10 bold", anchor=N, command=lambda i=24: BoardSize(i))
    button3.place(relx=0.5, rely=0.7, anchor=CENTER)
    startingButtons.append(button1)
    startingButtons.append(button2)
    startingButtons.append(button3)

SelectSize()
input()

另一个奇怪的事情是它引发了 2 个略有不同的错误,但它们仍然是同一个问题的一部分。以下是两个问题:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\idlelib\run.py", line 137, in main
    seq, request = rpc.request_queue.get(block=True, timeout=0.05)
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\queue.py", line 172, in get
    raise Empty
queue.Empty

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1698, in __call__
    args = self.subst(*args)
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1428, in _substitute
    e.type = EventType(T)
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\enum.py", line 291, in __call__
    return cls.__new__(cls, value)
RecursionError: maximum recursion depth exceeded while calling a Python object

还有一个:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\idlelib\run.py", line 137, in main
    seq, request = rpc.request_queue.get(block=True, timeout=0.05)
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\queue.py", line 172, in get
    raise Empty
queue.Empty

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1699, in __call__
    return self.func(*args)
  File "C:\Users\aidan\Desktop\MineSweeper\MinesweeperGUI.py", line 108, in Restart
    CreateWindow()
  File "C:\Users\aidan\Desktop\MineSweeper\MinesweeperGUI.py", line 153, in CreateWindow
    ButtonCreate()
  File "C:\Users\aidan\Desktop\MineSweeper\MinesweeperGUI.py", line 35, in ButtonCreate
    Button(root, text="Restart", width=8, height=1, font="Verdana 10 bold", command=Restart).grid(row=0, column=0, columnspan=3)
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 2363, in __init__
    Widget.__init__(self, master, 'button', cnf, kw)
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 2293, in __init__
    (widgetName, self._w) + extra + self._options(cnf))
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1320, in _options
    v = self._register(v)
  File "C:\Users\aidan\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1356, in _register
    f = CallWrapper(func, subst, self).__call__
RecursionError: maximum recursion depth exceeded

我已经尽可能多地查找了这个问题,它要么与 root.mainloop() 调用有关,要么与递归函数 (CheckNeighbours) 做错了什么有关,要么与我没有使用的事实有关类。感谢您的帮助:)

编辑 - 该程序似乎每次都会抛出一个略有不同的错误。但是,它们都以超出最大递归深度的错误结束

【问题讨论】:

  • 如果您遇到最大递归错误,几乎可以肯定是因为您的递归 CheckNeighbours 函数。如果您有一个 8x8 板,则板上的最大点数为 64。默认递归限制为 1000,这意味着您“检查邻居”1000 次,而要检查的邻居不可能超过 64 个。
  • 我知道你要去哪里。我已经解决了这个问题(因为我已经发布了一个解决方案),但就我而言,有超过 64 个邻居需要检查。这个程序还没有优化,因此,一块检查它周围的所有 8 个邻居,如果其中一个没有值(是一个空白点),那么它对那个瓷砖做同样的事情,它将检查所有 8 个邻居(除了在该图块上调用该函数的邻居)。
  • (cont) 然而,话虽如此,它实际上从未在一次调用中超过 1000 次递归,它会随着时间的推移而累积,即使前一组邻居检查的递归已经完成。 Python中递归的奇怪集成,但正如解决方案中所说,解决这个问题的最佳方法就是将它变成一个循环(由于我制作递归函数的方式,这需要一些额外的工作)。感谢您的帮助
  • python 的实现与任何其他编程语言没有什么不同。该行为是由于您的代码,而不是 python 的递归实现。
  • 就像补充一点,原来 mainloop() 是问题所在。这就是堆栈从未被删除或清除的原因。你确实是对的:)

标签: python tkinter


【解决方案1】:

问题或至少一个问题是您不止一次调用mainloop,并且您是从事件处理程序调用它。这就是最终导致您无限递归的原因。

顾名思义,mainloop 本身就是一个无限循环。它一直运行到主窗口被破坏。当您按下某个键或按钮时,mainloop 会运行与该键或按钮关联的命令。如果该命令是MinePressed,它最终将导致mainloop 从原来的mainloop 调用内部再次被调用。

因为mainloop 永远不会退出(因为根窗口永远不会被破坏),所以您有一个无限循环调用一个无限循环调用一个无限循环,...,并且这些内部循环都不会退出。最终,当您运行一千个mainloop 副本时,您的堆栈空间就会用完。

您需要将root.mainloop() 移出MinePressed 并将其放在文件的最后一行,例如:

SelectSize()
input()
root.mainloop()

不过,它可能在input() 之前——我不知道那行代码是干什么用的。

【讨论】:

  • 非常感谢!这就解释了为什么堆栈空间没有被清除,从而将我的递归循环堆叠在一起,直到达到最大递归限制。 input() 存在的原因实际上是为了让程序不会关闭,但现在我明白了 mainloop() 的作用,它可以完全替换 input() 语句!感谢您的帮助!
  • 我现在遇到了一些难以解释的问题。当我点击一个按钮时,它应该进入relief = SUNKEN模式,我已经检查过了,它甚至执行该行,以及该行中的所有其他命令('MinePressed'中'root.update_idletasks()'上方的行)。但是,除非我将 root.mainloop() 保留在那里,否则它不会保持凹陷。最奇怪的部分是,每个通过 CheckNeighbours() 函数更新并成功调用relief=SUNKEN 的按钮都保持凹陷状态。感谢您的帮助
  • 也修复了上一个问题。只需将 'root.mainloop()' 放在文件末尾,删除所有其他 'root.mainloop()' 和 'root.update_idletasks()',并更改此行: button.bind(' ', lambda event, x=x, y=y: MinePressed(x, y, False)) 到这个:button.config(command=lamda x=x, y=y: MinePressed(x, y, False)) 谢谢对于所有的帮助布莱恩
猜你喜欢
  • 2017-11-07
  • 1970-01-01
  • 2021-01-10
  • 1970-01-01
  • 1970-01-01
  • 2020-06-25
  • 2020-01-05
  • 2020-07-13
  • 1970-01-01
相关资源
最近更新 更多