使用Python在Tkinter中保存异常



我为其他人开发了几个Python程序,这些程序使用Tkinter接收用户的输入。为了保持简单和用户友好,命令行或python控制台永远不会被打开(即使用.pyw文件),所以我正在考虑在发生异常时使用日志库将错误文本写入文件。然而,我很难让它真正捕捉到异常。例如:

我们编写了一个会导致错误的函数:

def cause_an_error():
    a = 3/0

现在我们尝试正常记录错误:

import logging
logging.basicConfig(filename='errors.log', level=logging.ERROR)
try:
    cause_an_error()
except:
    logging.exception('simple-exception')

不出所料,程序会出错,日志记录会将错误写入errors.log。控制台中不会显示任何内容。然而,当我们实现Tkinter接口时,会有不同的结果,比如:

import logging
import Tkinter
logging.basicConfig(filename='errors.log', level=logging.ERROR)
try:
    root = Tkinter.Tk()
    Tkinter.Button(root, text='Test', command=cause_an_error).pack()
    root.mainloop()
except:
    logging.exception('simple-exception')

在这种情况下,按下Tkinter窗口中的按钮会导致错误。然而,这一次,文件中没有写入任何内容,控制台中出现以下错误:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:Python27liblib-tkTkinter.py", line 1536, in __call__
    return self.func(*args)
  File "C:/Users/samk/Documents/GitHub/sandbox/sandbox2.pyw", line 8, in cause_an_error
    a = 3/0
ZeroDivisionError: integer division or modulo by zero

是否有其他方法可以捕获并记录此错误?

虽然没有很好的文档,但tkinter会为回调导致的异常调用一个方法。通过在根窗口上设置属性report_callback_exception,您可以编写自己的方法来执行任何您想要的操作。

例如:

import tkinter as tk
def handle_exception(exception, value, traceback):
    print("Caught exception:", exception)
def raise_error():
    raise Exception("Sad trombone!")
root = tk.Tk()
# setup custom exception handling
root.report_callback_exception=handle_exception
# create button that causes exception
b = tk.Button(root, text="Generate Exception", command=raise_error)
b.pack(padx=20, pady=20)
root.mainloop()

供参考:

  • http://epydoc.sourceforge.net/stdlib/Tkinter.Tk-class.html#report_callback_exception

由于这个问题是关于日志记录和明确错误发生的位置,我将详细介绍Bryan(完全正确)的答案。

首先,您必须导入一些其他有用的模块。

import logging
import functools  # for the logging decorator
import traceback  # for printing the traceback to the log

然后,您必须配置记录器和日志记录功能:

logging.basicConfig(filename='/full/path/to_your/app.log',
                    filemode='w',
                    level=logging.INFO,
                    format='%(levelname)s: %(message)s')
def log(func):
    # logging decorator
    @functools.wraps(func)
    def wrapper_log(*args, **kwargs):
        msg = ' '.join(map(str, args))
        level = kwargs.get('level', logging.INFO)
        if level == logging.INFO:
            logging.info(msg)
        elif level == logging.ERROR:
            logging.exception(msg)
            traceback.print_exc()
    return wrapper_log

@log
def print_to_log(s):
    pass

请注意,执行日志记录的函数实际上是log,您可以使用它将错误和常规信息打印到日志文件中。如何使用:函数print_to_loglog装饰。您可以放置一些常规的print(),而不是pass,这样您就可以将消息保存到日志中,并将其打印到控制台。您可以用您喜欢的任何命令替换pass

注nb。2我们使用log函数中的回溯来跟踪您的代码到底在哪里生成了错误。

要处理异常,请按照已接受的答案进行操作。另外,您将消息(即回溯)传递给print_to_log函数:

def handle_exception(exc_type, exc_value, exc_traceback):
    # prints traceback in the log
    message = ''.join(traceback.format_exception(exc_type,
                                                 exc_value,
                                                 exc_traceback))
    print_to_log(message)

配置了所有这些之后,现在您告诉您的Tkinter应用程序,它必须使用handle_exception功能:

class App(tk.Tk):
    # [the rest of your app code]
if __name__ == '__main__':
    app = App()
    app.report_callback_exception = handle_exception
    app.mainloop()

或者,如果您使用多个类,您甚至可以指示类本身使用andle_exception函数:

class App(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        tk.Tk.report_callback_exception = handle_exception
        # [the rest of your app code]
if __name__ == '__main__':
    app = App()
    app.mainloop()

这样,你的应用程序代码看起来很瘦,你不需要任何try/except方法,你可以在信息级别记录应用程序中的任何事件。

最新更新