5

我用 Python Tkinter 编写了一个应用程序。我最近注意到,对于其中一项操作,如果该操作失败,它有时会关闭(没有给出任何错误)。我写了一个小程序来说明这个问题:-

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

要重现该问题,请在“ms 命令提示符”中打开它将重命名的文件夹,以便重命名它会从 Tkinter 代码中引发异常。

我的原始代码使用线程并且也在执行其他任务,所以我试图使这个测试脚本中的操作尽可能相似。

现在,如果我通过双击运行此代码,则程序会简单地关闭而不会引发任何错误。但是如果我一直从控制台运行这个脚本,那么异常消息会被转储到控制台上,至少我知道,出了点问题。

我可以通过在尝试重命名的代码中使用 try/catch 来修复此代码,但我也想通知用户此失败。所以我只想知道在编写 Tkinter 应用程序时应该遵循哪些编码方法,我想知道:-

1)每当用户通过双击运行它时,我可以让我的脚本在文件中转储一些堆栈跟踪吗?至少,我会知道出了点问题并修复它。

2) 我可以阻止 tkinter 应用程序在出现此类错误时退出并在某些 TK 对话框中抛出任何异常。

感谢帮助!!

4

4 回答 4

14

我看到您有一个非面向对象的示例,所以我将展示两个变体来解决异常捕获问题。

密钥在tkinter\__init__.py文件中。可以看到有一个文档report_callback_exception化的Tk类方法。这是它的描述:

report_callback_exception()

在 sys.stderr 上报告回调异常。

应用程序可能想要覆盖这个内部函数,并且应该在 sys.stderr 为 None 时。

所以我们看到它应该覆盖这个方法,让我们去做吧!

非面向对象的解决方案

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

面向对象的解决方案

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

结果

我的环境:

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
于 2016-01-28T22:04:30.093 回答
5

您可以覆盖 Tkinter 的CallWrapper. 为此,必须使用 Tkinter 的命名导入而不是通配符导入:

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()
于 2013-03-06T14:13:07.860 回答
3

我不太确定我是否已经很好地理解了您,但是这个简单的代码使您可以控制找不到目录的情况:

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

编辑:由于您想要一个通用方法并且 Tkinter 不传播异常,因此您必须对其进行编程。有两种方法:

1)像我在上面的例子中所做的那样将它硬编码到函数中(可怕)

2) 使用装饰器添加 try-except 块。

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

编辑:如果你想包括堆栈

include traceback

在 except 块中:

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

A.Rodas 提出的解决方案更简洁、更完整,但更难理解。

于 2013-03-06T12:10:46.437 回答
0

您可以使用 except-hook() 为异常安装全局处理程序。一个例子可以在这里找到。

于 2013-03-06T13:04:41.840 回答