2013-03-06 3 views
3

Я написал приложение в Python Tkinter. Недавно я заметил, что для одной из операций она иногда закрывается (без каких-либо ошибок), если эта операция завершилась неудачно. Я написал небольшую программу, чтобы проиллюстрировать эту проблему: -Обработка исключения в 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() 

Чтобы воспроизвести проблему, откройте папку, которая будет переименовывать в «мс командной строки» таким образом, чтобы переименовать его выбросит исключение из Tkinter кода.

Мой исходный код использует потоки и выполняет другие задачи, поэтому я попытался сделать операции в этом тестовом скрипте как можно более похожими.

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

Я могу исправить этот код с помощью try/catch в коде, в котором он пытался переименовать, но я хочу сообщить об этом и об этом пользователе. Поэтому я просто хочу знать, какие подходы к кодированию следует соблюдать при написании Tkinter App, и я хочу знать:

1) Могу ли я заставить мой сценарий выгружать трассировку стека в файл всякий раз, когда пользователь запускал это, дважды щелкая по нему , По крайней мере, я знал бы, что что-то не так и исправить.

2) Могу ли я запретить приложению tkinter выйти из такой ошибки и вызвать любое исключение в некотором диалоге TK.

Спасибо за помощь!

+0

Просто уведомление, вы должны объявить master global только в том случае, если вы переназначили его, но вы просто вызываете его метод, поэтому объявление не нужно. – lolopop

+0

Да, вы правы. Нет необходимости объявлять master как глобальный объект. – sarbjit

+0

Опубликуйте свое решение как ответ, вместо того, чтобы добавить его к вопросу. –

ответ

4

Вы можете изменить Tkinter's CallWrapper, как описано here.Необходимо использовать именованный импорт 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() 
+0

Я включил это в свой код, но некоторые из них, похоже, не работают. При двойном щелчке по коду, он выступы просто выходят без записи какого-либо файла. Смотрите мой обновленный код. – sarbjit

+0

@sarbjit Я не пробовал код выше, но, похоже, работает только при импорте. Я обновил ответ. –

+0

С именем import, он отлично работает. Хотя мне пришлось добавить код, чтобы отменить все ожидающие Tk после событий и закрыть mainloop явно. – sarbjit

2

Я не очень уверен, что если я хорошо вас понял, но этот простой код дает вам контроль над случаем, в котором каталог не может быть найдено:

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: Так как вы хотите общий метод и Tkinter не распространяют исключения, вам необходимо запрограммировать его. Есть два способа:

1) жёстко его в функции, как я сделал в приведенном выше примере (ужасный)

2) Используйте декоратор, чтобы добавить примерочных, за исключением блока.

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: Если вы хотите включить в стек

include traceback 

и в за исключением блока:

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

решение, которое предложил A.Rodas чище и более но сложнее понять.

+0

Я действительно ищу универсальное решение для обработки исключений в случае приложений Tkinter. Как и в этом случае, я знаю об этом решении. Я хочу знать, что если есть способ, с помощью которого любое исключение, созданное в приложении Python Tkinter, выгружается в текстовый файл. – sarbjit

+0

@sarbjit Я расширил свое решение. Проверьте это, теперь я понимаю, что вы хотели, и я уверен, что это поможет. – bgusach

+0

Спасибо за решение, это то, что я ищу. Поскольку я изучаю Python, у меня мало сомнений и надеюсь, что вы поможете уточнить: 1) Когда вы использовали '@ ProvideException' в коде, что это значит, что мы называем этим в Python (я хочу прочитать об этом). 2) С помощью этого метода можно сбросить полную трассировку стека. Поэтому я хочу, чтобы всякий раз, когда возникало какое-либо исключение, он должен быть сброшен в текстовый файл. На данный момент отображается только строковое сообщение об ошибке. Также это будет обрабатывать все виды исключений, правильно? – sarbjit

0

Вы можете установить глобальный обработчик для исключения с помощью except-hook(). Пример можно найти here.

5

я вижу, у вас есть не-объектно-ориентированный пример, поэтому я покажу два варианта решения проблемы исключения.

Ключ в файле 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() 

The result

Моя среда:

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 
+0

Если вы хотите сообщить о полной трассировке для ошибки, используйте 'message = traceback.format_exc()' ([кредит Джеймсу] (https://stackoverflow.com/a/37493588/3357935)) –

Смежные вопросы