Search code examples
pythonpython-3.xtkinterrecursion

Is there any way to catch and handle infinite recursive functions that create Tkinter windows?


I have a student that submitted a function like this:

def infinite_windows():
    window = tkinter.Tk()
    infinite_windows()

I already test things inside a try-except block. My code reports the RecursionError, but then it freezes my desktop the next time it asks for user input and I have to kill Python. The following code hangs, but only when the input() call is included:

import tkinter

def infinite_windows():
    window = tkinter.Tk()
    infinite_windows()
    
try:
    infinite_windows()
except Exception as e:
    print("caught the exception!")
    print(e)

input("hi there") #hangs here

Is there any way for me to handle this behavior and continue running Python without removing the call to input()?


Solution

  • I got this as a possible solution:

    import tkinter
    
    
    def infinite_windows():
        window = tkinter.Tk()
        infinite_windows()
    
    
    # Store all Tk objects in an array
    _all_windows = []
    
    class _Tk(tkinter.Tk):
        def __init__(self, *args, **kwargs):
            _all_windows.append(self) # Add this Tk object in the array
            super().__init__(*args, **kwargs)
    tkinter.Tk = _Tk # Replace tkinter's Tk class with our own
    
    
    try:
        infinite_windows()
    except Exception as e:
        print("caught the exception!")
        print(e)
    
    # Loop through and destroy all `tkinter.Tk` objects
    for window in _all_windows:
        try:
            window.destroy()
        except tkinter.TclError:
            # If the window wasn't fully created/already was destroyed
            pass
    _all_windows.clear() # Clear the array
    
    input("? ")
    

    Basically I overwrite tkinter.Tk with my own class that inherits from tkinter.Tk (doesn't cause recursion problems because my class is constructed before I replace tkinter.Tk). In my own class, I keep track of all tkinter.Tk objects in a global list and destroy them at the end.

    This approach works (on Ubuntu with Wayland) but has a few limitations:

    • Doesn't actually release the 1.9 GB of RAM that is needed for all of the tkinter.Tk objects. Nothing we can really do about that because python is reluctant to release RAM back to the OS
    • This doesn't work if the student does something that stores the original tkinter.Tk class like this:
    import tkinter
    
    def infinite_windows(window_class=tkinter.Tk):
        window = window_class()
        infinite_windows()
    

    To solve that issue, you can move the code that overwrites tkinter.Tk before the student's code (but that will raise an error if the student tries to import something from __future__)