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:

    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__)