Как я могу делать тихие исключения громче в tkinter?

Если я запустил следующий код с терминала, я получаю сообщение об ошибке в терминале:

import Tkinter as tk

master = tk.Tk()

def callback():
    raise UserWarning("Exception!")

b = tk.Button(master, text="This will raise an exception", command=callback)
b.pack()

tk.mainloop()

Однако, если я запустил его без терминала (например, дважды щелкнув значок), сообщение об ошибке будет отключено.

В моем реальном, более сложном приложении Tkinter мне нравится, что GUI немного устойчив к сбоям. Мне не нравится, что мои пользователи с трудом дают мне полезные отзывы, чтобы исправить возникающее неожиданное поведение.

Как мне это сделать? Существует ли стандартный способ обнаружения трассировки или stderror или еще чего-то в приложении Tkinter? Я ищу что-то более элегантное, чем ставить try/except везде.

EDIT: Йохен Ритцел дал отличный ответ ниже, который выдает предупреждение и упоминает о присоединении к классу. Чтобы сделать это явным:

import Tkinter as tk
import traceback, tkMessageBox

class App:
    def __init__(self, master):
        master.report_callback_exception = self.report_callback_exception
        self.frame = tk.Frame(master)
        self.frame.pack()
        b = tk.Button(
            self.frame, text="This will cause an exception",
            command=self.cause_exception)
        b.pack()

    def cause_exception(self):
        a = []
        a.a = 0 #A traceback makes this easy to catch and fix

    def report_callback_exception(self, *args):
        err = traceback.format_exception(*args)
        tkMessageBox.showerror('Exception', err)

root = tk.Tk()
app = App(root)
root.mainloop()

Мое оставшееся замешательство: Йохен упоминает о возможности создания разных функций отчетности об исключениях в разных кадрах. Я еще не вижу, как это сделать. Это очевидно?

Ответы

Ответ 1

Для этого есть report_callback_exception:

import traceback
import tkMessageBox

# You would normally put that on the App class
def show_error(self, *args):
    err = traceback.format_exception(*args)
    tkMessageBox.showerror('Exception',err)
# but this works too
tk.Tk.report_callback_exception = show_error

Если вы не импортировали "Tkinter as tk", тогда выполните

Tkinter.Tk.report_callback_exception = show_error

Ответ 2

Сначала выполните следующее: только сегодня патч на трекере CPython для docstring tkinter.Tk.report_callback_exception дал понять, что решение Jochen предназначено. Патч также (и в первую очередь) остановил tk от сбоев при исключениях обратного вызова при запуске под управлением pythonw в Windows.

Во-вторых: вот начало stderr решения для создания функции stderr без консоли (это действительно должен быть отдельный вопрос SO).

import sys, tkinter

root = tkinter.Tk()

class Stderr(tkinter.Toplevel):
    def __init__(self):
        self.txt = tkinter.Text(root)
        self.txt.pack()
    def write(self, s):
        self.txt.insert('insert', s)

sys.stderr = Stderr()

1/0 # traceback appears in window

Необходимо больше, чтобы окно всплывающего окна было скрыто до тех пор, пока оно не понадобится, а затем сделайте его видимым.