sys.exit vs root.destroy

21 May 2022

It’s been almost a month since my last post and I’ve been trying to come up with a topic that is relevant. I’ve had a number of thoughts on what to post about, but every time I come up with something, I get sidetracked by another project or just life. This week, I had to “eat crow” on an error that was found in my Widget Demo that was included in PAGE 7.4 and to fight many other issues in life in general. I had thought I’d use that as a topic for a post. However, yesterday (Friday) I was working on a program that I was going to use as part of an article for the upcoming June issue of Full Circle Magazine, but I ran into an issue.

(Cue the wibbly wobbly visual effect that suggests we are going back to the past in our minds)

A number of years ago, a friend who was testing a PAGE program I had written and came up with an issue that I had never seen before. At that time I was using Geany as my IDE and was very happy with it. My only problem with it was that it couldn’t provide an integrated Debugger. But that was OK, since I could throw in a bunch of print() statements and find the issue. My friend, however was using IDLE as his IDE, which is the default IDE for Python.

His problem was that when he tried to exit my program, he clicked the Exit button, the IDLE Python shell showed that the program had exited, but the GUI was still on the screen. I knew that in my IDE, and when I ran the program from the terminal, the program exited properly. I could not understand why he was having issues when I wasn’t. I fired up a local copy of IDLE and started the program. When I clicked the Exit button, sure enough, the GUI stayed on the screen while the IDLE Python shell showed that the program had ended.

I can’t tell you how many times that I tried to fight the issue and came up with no answers. After a number of hours, I finally came up with an answer. My callback for the Exit button used the call sys.exit() to end the program. Now you would think that sys.exit() would work on anything, since that’s pretty much the stand for any type of Python program. I started digging into the issue, and couldn’t find any reason for sys.exit() not to properly end the program and destroy the window. Remember, this was so many years ago when the PAGE version was somewhere around version 4. At that time, PAGE created a function called destroy_window() which calls a GUI function that destroys the toplevel form and sets the value to None. I had completely ignored this function since PAGE version 3. So I changed the Exit function to call destroy_window() rather than sys.exit() in the IDLE editor and re-ran the program. It worked. Not only did the program properly end in the integrated shell, but the GUI properly closed.

Confused, I went back to my copy of Geany and ran the program, this time using the PAGE supplied function destroy_window(). It ran properly and exited properly as well. I told my friend what I had found and told him that I made a conscious decision to use the destroy_window() function that PAGE provides from now on, instead of the sys.exit() that I had used for years.

(Cue the wibbly wobbly visual effect that suggests we are returning to the present in our minds)

Man, that wibbly wobbly visual effect messes with my mind. Anyway, now back to the present, I was working on the program for my Full Circle Article in my current IDE (which is VS Code for Linux) and when I exited the program, I got a number of errors when the screen closed. I had seen this before, and noticed that it only happens when I started the program using the _support.py file either from my IDE or from the terminal directly in Python. I knew that something was not getting deleted properly on Python’s internal cleanup routines from the error messages…

Exception ignored in: <function Image.__del__ at 0x7fee1f4e8c20>
Traceback (most recent call last):
  File "python3.7/tkinter/__init__.py", line 3508, in __del__
TypeError: catching classes that do not inherit from BaseException is not allowed

When I’ve seen this before, I was under a major time crunch and decided that the “normal” user would never run the program from the _support.py module, since it’s easier to leave off the ‘_support’ part when typing from the terminal. So, I put on my magical Sherlock Holmes hat and jumped into investigative mode. Up to this point, all my web searches concentrated on just search terms like “python”, “tkinter” and a mention of the error message and all gave me a few links, but nothing good and definitive as the to cause.

This time, however, I changed my search terms to focus on ‘tkinter’ and ‘Image.__del’ and this time, I got a link that pointed me to a tkinter bug report from 2013 that comes from an issue that was discovered in Python 3.4 when using turtle graphics under tkinter. If it was ever actually fixed, it has come back.

It turns out that somewhere in the tkinter.root.destroy() method, the images are not being cleared so they still exist when Python then tries to do it’s garbage collection routines. Apparently, the Tk.destroy does nothing with images. So I’m not entirely sure if it is a tkinter issue or a Tk issue. In my mind, it should be tkinter’s responsibility to delete the images before calling Tk.destroy, but what do I know?

Anyway, I found a couple of workarounds for this problem. First, you can not include any images in the PAGE toplevel forms that make up your GUI. Then in the _support module, simple create a function that you run at startup to load all your images using PIL (pillow). If you do this, the PIL library will handle the deletion of the images before the Tk.destroy gets called. As to the second workaround method, when PAGE 7 came out, the original destroy_window() function doesn’t get created and the root.destroy() should be called. However, this gets back to the original issue. So you can call (guess what?) sys.exit to exit your program. However, we’ve entered the original issue so if you use IDLE as your IDE, you will have the problems that I described from way back when.

So, here we stand. We can either not utilize PAGE to it’s complete functionality and not include images in our design or go back to sys.exit() and loose the ability for programmers to use IDLE as their IDE. I don’t know which is better, and I’m not going to try to tell you which is best. My function here is simply to be something similar to the town crier of old (something else that has gone away due to technology) (see https://en.wikipedia.org/wiki/Town_crier for information on this old and nobel profession.) and raise awareness of an issue. For me, as a teacher type, I don’t have a clear cut path. At this point, I think I’ll just sit in the shadows and wring my hands in frustration.

Until we type again, Enjoy life, be safe and keep coding!

Greg