使用Python在Tkinter中保存异常

发布时间 2023-11-24 09:18:34作者: mingruqi

我为其他使用Tkinter接收用户输入的人开发了几个Python程序。为了保持简单和用户友好,命令行或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:\Python27\lib\lib-tk\Tkinter.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()

由于这个问题是关于日志记录和明确错误发生的地方,我将扩展到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_log修饰的函数log。而不是pass,您可以放置一些常规的print(),以便将消息保存到日志中,并将其打印到控制台。您可以用您喜欢的任何命令替换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()

通过这种方式,您的应用程序代码看起来很简洁,您不需要任何尝试/除方法,并且您可以在信息级别记录您的应用程序中的任何事件。