sarbjit
sarbjit

Reputation: 3894

Handling exception in python tkinter

I have written an application in Python Tkinter. I recently noticed that for one of the operation, it sometimes closes (without giving any error) if that operation failed. I have written a small program to illustrate the problem :-

import os
from Tkinter import *

def copydir():
    src = "D:\\a\\x\\y"
    dest = "D:\\a\\x\\z"
    os.rename(src,dest)

master = Tk()

def callback():
    global master
    master.after(1, callback)
    copydir()
    print "click!"

b = Button(master, text="OK", command=copydir)
b.pack()

master.after(100, callback)

mainloop()

To reproduce the problem, open the folder which it will rename in “ms command prompt” such that renaming it will throw exception from Tkinter code.

My original code is using threading and is performing other tasks as well, so I have tried to make the operations in this test script as similar as possible.

Now, if I run this code by double clicking it, then program simply closes without throwing any error. But If I had been running this script from console, then exception messages are dumped on the console and atleast I got to know , something is wrong.

I can fix this code by using try/catch in the code where it tried to rename but I want to inform user about this failure as well. So I just want to know what coding approaches should be followed while writing Tkinter App's and I want to know:-

1) Can I make my script dump some stack trace in a file whenever user ran this by double clicking on it. By this atleast, I would know something is wrong and fix it.

2) Can I prevent the tkinter app to exit on such error and throw any exception in some TK dialog.

Thanks for help!!

Upvotes: 6

Views: 24204

Answers (4)

A. Rodas
A. Rodas

Reputation: 20679

You can override Tkinter's CallWrapper. It is necessary to use a named import of Tkinter instead of a wildcard import in order to do so:

import Tkinter as tk
import traceback

class Catcher: 
    def __init__(self, func, subst, widget):
        self.func = func 
        self.subst = subst
        self.widget = widget
    def __call__(self, *args):
        try:
            if self.subst:
                args = apply(self.subst, args)
            return apply(self.func, args)
        except SystemExit, msg:
            raise SystemExit, msg
        except:
            traceback.print_exc(file=open('test.log', 'a'))

# ...
tk.CallWrapper = Catcher
b = tk.Button(master, text="OK", command=copydir)
b.pack()
master.mainloop()

Upvotes: 5

Maxim Kochurov
Maxim Kochurov

Reputation: 301

I see you have a non-object oriented example, so I'll show two variants to solve the problem of exception-catching.

The key is in the in the tkinter\__init__.py file. One can see that there is a documented method report_callback_exception of Tk class. Here is its description:

report_callback_exception()

Report callback exception on sys.stderr.

Applications may want to override this internal function, and should when sys.stderr is None.

So as we see it it is supposed to override this method, lets do it!

Non-object oriented solution

import tkinter as tk
from tkinter.messagebox import showerror


if __name__ == '__main__':

    def bad():
        raise Exception("I'm Bad!")

    # any name as accepted but not signature
    def report_callback_exception(self, exc, val, tb):
        showerror("Error", message=str(val))

    tk.Tk.report_callback_exception = report_callback_exception
    # now method is overridden

    app = tk.Tk()
    tk.Button(master=app, text="bad", command=bad).pack()
    app.mainloop()

Object oriented solution

import tkinter as tk
from tkinter.messagebox import showerror


class Bad(tk.Tk):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # or tk.Tk.__init__(*args, **kwargs)

        def bad():
            raise Exception("I'm Bad!")
        tk.Button(self, text="bad", command=bad).pack()

    def report_callback_exception(self, exc, val, tb):
        showerror("Error", message=str(val))

if __name__ == '__main__':

    app = Bad()
    app.mainloop()

The result

My environment:

Python 3.5.1 |Anaconda 2.4.1 (64-bit)| (default, Dec  7 2015, 15:00:12) [MSC  
v.1900 64 bit (AMD64)] on win32

tkinter.TkVersion
8.6

tkinter.TclVersion
8.6

Upvotes: 17

bgusach
bgusach

Reputation: 15153

I am not very sure if I have understood you well, but this simple code gives you control over the case in which the directory could not be found:

import os
from Tkinter import *

def copydir():
    src = "D:\\troll"
    dest = "D:\\trollo"

    try:
        os.rename(src, dest)
    except:
        print 'Sorry, I couldnt rename'
        # optionally: raise YourCustomException
        # or use a Tkinter popup to let the user know

master = Tk()

b = Button(master, text="OK", command=copydir)
b.pack()

mainloop()

EDIT: Since you want a general method and Tkinter does not propagate exceptions, you have to program it. There are two ways:

1) Hardcode it into the the functions as I did in the example above (horrible)

2) Use a decorator to add a try-except block.

import os
from Tkinter import *


class ProvideException(object):
    def __init__(self, func):
        self._func = func

    def __call__(self, *args):

        try:
            return self._func(*args)

        except Exception, e:
            print 'Exception was thrown', str(e)
            # Optionally raise your own exceptions, popups etc


@ProvideException
def copydir():
    src = "D:\\troll"
    dest = "D:\\trollo"

    os.rename(src, dest)

master = Tk()

b = Button(master, text="OK", command=copydir)
b.pack()

mainloop()

EDIT: If you want to include the stack

include traceback

and in the except block:

except Exception, e:
    print 'Exception was thrown', str(e)
    print traceback.print_stack()

The solution that A.Rodas has proposed is cleaner and more complete, however, more complicated to understand.

Upvotes: 3

krase
krase

Reputation: 1044

You can install a global handler for exception with a except-hook(). An example can be found here.

Upvotes: 0

Related Questions